@benjavicente/router-core 1.168.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/bin/intent.js +25 -0
- package/dist/cjs/Matches.cjs +17 -0
- package/dist/cjs/Matches.cjs.map +1 -0
- package/dist/cjs/Matches.d.cts +139 -0
- package/dist/cjs/RouterProvider.d.cts +27 -0
- package/dist/cjs/config.cjs +11 -0
- package/dist/cjs/config.cjs.map +1 -0
- package/dist/cjs/config.d.cts +17 -0
- package/dist/cjs/defer.cjs +41 -0
- package/dist/cjs/defer.cjs.map +1 -0
- package/dist/cjs/defer.d.cts +37 -0
- package/dist/cjs/fileRoute.d.cts +24 -0
- package/dist/cjs/global.d.cts +7 -0
- package/dist/cjs/hash-scroll.cjs +20 -0
- package/dist/cjs/hash-scroll.cjs.map +1 -0
- package/dist/cjs/hash-scroll.d.cts +7 -0
- package/dist/cjs/history.d.cts +8 -0
- package/dist/cjs/index.cjs +96 -0
- package/dist/cjs/index.d.cts +53 -0
- package/dist/cjs/invariant.cjs +8 -0
- package/dist/cjs/invariant.cjs.map +1 -0
- package/dist/cjs/invariant.d.cts +1 -0
- package/dist/cjs/isServer/client.cjs +7 -0
- package/dist/cjs/isServer/client.cjs.map +1 -0
- package/dist/cjs/isServer/client.d.cts +1 -0
- package/dist/cjs/isServer/development.cjs +7 -0
- package/dist/cjs/isServer/development.cjs.map +1 -0
- package/dist/cjs/isServer/development.d.cts +1 -0
- package/dist/cjs/isServer/server.cjs +7 -0
- package/dist/cjs/isServer/server.cjs.map +1 -0
- package/dist/cjs/isServer/server.d.cts +1 -0
- package/dist/cjs/link.cjs +6 -0
- package/dist/cjs/link.cjs.map +1 -0
- package/dist/cjs/link.d.cts +221 -0
- package/dist/cjs/load-matches.cjs +659 -0
- package/dist/cjs/load-matches.cjs.map +1 -0
- package/dist/cjs/load-matches.d.cts +18 -0
- package/dist/cjs/location.d.cts +50 -0
- package/dist/cjs/lru-cache.cjs +70 -0
- package/dist/cjs/lru-cache.cjs.map +1 -0
- package/dist/cjs/lru-cache.d.cts +6 -0
- package/dist/cjs/manifest.cjs +18 -0
- package/dist/cjs/manifest.cjs.map +1 -0
- package/dist/cjs/manifest.d.cts +35 -0
- package/dist/cjs/new-process-route-tree.cjs +754 -0
- package/dist/cjs/new-process-route-tree.cjs.map +1 -0
- package/dist/cjs/new-process-route-tree.d.cts +236 -0
- package/dist/cjs/not-found.cjs +26 -0
- package/dist/cjs/not-found.cjs.map +1 -0
- package/dist/cjs/not-found.d.cts +32 -0
- package/dist/cjs/path.cjs +252 -0
- package/dist/cjs/path.cjs.map +1 -0
- package/dist/cjs/path.d.cts +56 -0
- package/dist/cjs/qss.cjs +70 -0
- package/dist/cjs/qss.cjs.map +1 -0
- package/dist/cjs/qss.d.cts +33 -0
- package/dist/cjs/redirect.cjs +56 -0
- package/dist/cjs/redirect.cjs.map +1 -0
- package/dist/cjs/redirect.d.cts +77 -0
- package/dist/cjs/rewrite.cjs +68 -0
- package/dist/cjs/rewrite.cjs.map +1 -0
- package/dist/cjs/rewrite.d.cts +30 -0
- package/dist/cjs/root.cjs +7 -0
- package/dist/cjs/root.cjs.map +1 -0
- package/dist/cjs/root.d.cts +3 -0
- package/dist/cjs/route.cjs +100 -0
- package/dist/cjs/route.cjs.map +1 -0
- package/dist/cjs/route.d.cts +552 -0
- package/dist/cjs/routeInfo.d.cts +54 -0
- package/dist/cjs/router.cjs +1173 -0
- package/dist/cjs/router.cjs.map +1 -0
- package/dist/cjs/router.d.cts +734 -0
- package/dist/cjs/scroll-restoration-inline.cjs +6 -0
- package/dist/cjs/scroll-restoration-inline.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-inline.d.cts +6 -0
- package/dist/cjs/scroll-restoration-script/client.cjs +9 -0
- package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-script/client.d.cts +2 -0
- package/dist/cjs/scroll-restoration-script/server.cjs +30 -0
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -0
- package/dist/cjs/scroll-restoration-script/server.d.cts +2 -0
- package/dist/cjs/scroll-restoration.cjs +191 -0
- package/dist/cjs/scroll-restoration.cjs.map +1 -0
- package/dist/cjs/scroll-restoration.d.cts +38 -0
- package/dist/cjs/searchMiddleware.cjs +55 -0
- package/dist/cjs/searchMiddleware.cjs.map +1 -0
- package/dist/cjs/searchMiddleware.d.cts +25 -0
- package/dist/cjs/searchParams.cjs +65 -0
- package/dist/cjs/searchParams.cjs.map +1 -0
- package/dist/cjs/searchParams.d.cts +31 -0
- package/dist/cjs/ssr/client.cjs +7 -0
- package/dist/cjs/ssr/client.d.cts +6 -0
- package/dist/cjs/ssr/constants.cjs +8 -0
- package/dist/cjs/ssr/constants.cjs.map +1 -0
- package/dist/cjs/ssr/constants.d.cts +3 -0
- package/dist/cjs/ssr/createRequestHandler.cjs +44 -0
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -0
- package/dist/cjs/ssr/createRequestHandler.d.cts +9 -0
- package/dist/cjs/ssr/handlerCallback.cjs +8 -0
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -0
- package/dist/cjs/ssr/handlerCallback.d.cts +9 -0
- package/dist/cjs/ssr/headers.cjs +21 -0
- package/dist/cjs/ssr/headers.cjs.map +1 -0
- package/dist/cjs/ssr/headers.d.cts +3 -0
- package/dist/cjs/ssr/json.cjs +11 -0
- package/dist/cjs/ssr/json.cjs.map +1 -0
- package/dist/cjs/ssr/json.d.cts +10 -0
- package/dist/cjs/ssr/serializer/RawStream.cjs +287 -0
- package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/RawStream.d.cts +64 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +32 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +9 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +13 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
- package/dist/cjs/ssr/serializer/transformer.cjs +53 -0
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/transformer.d.cts +91 -0
- package/dist/cjs/ssr/server.cjs +13 -0
- package/dist/cjs/ssr/server.d.cts +6 -0
- package/dist/cjs/ssr/ssr-client.cjs +183 -0
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-client.d.cts +10 -0
- package/dist/cjs/ssr/ssr-match-id.cjs +12 -0
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-match-id.d.cts +2 -0
- package/dist/cjs/ssr/ssr-server.cjs +279 -0
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -0
- package/dist/cjs/ssr/ssr-server.d.cts +42 -0
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +327 -0
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -0
- package/dist/cjs/ssr/transformStreamWithRouter.d.cts +11 -0
- package/dist/cjs/ssr/tsrScript.cjs +6 -0
- package/dist/cjs/ssr/tsrScript.cjs.map +1 -0
- package/dist/cjs/ssr/tsrScript.d.cts +1 -0
- package/dist/cjs/ssr/types.d.cts +30 -0
- package/dist/cjs/stores.cjs +148 -0
- package/dist/cjs/stores.cjs.map +1 -0
- package/dist/cjs/stores.d.cts +70 -0
- package/dist/cjs/structuralSharing.d.cts +4 -0
- package/dist/cjs/typePrimitives.d.cts +65 -0
- package/dist/cjs/useLoaderData.d.cts +5 -0
- package/dist/cjs/useLoaderDeps.d.cts +5 -0
- package/dist/cjs/useNavigate.d.cts +3 -0
- package/dist/cjs/useParams.d.cts +5 -0
- package/dist/cjs/useRouteContext.d.cts +9 -0
- package/dist/cjs/useSearch.d.cts +5 -0
- package/dist/cjs/utils.cjs +339 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +178 -0
- package/dist/cjs/validators.d.cts +51 -0
- package/dist/esm/Matches.d.ts +139 -0
- package/dist/esm/Matches.js +17 -0
- package/dist/esm/Matches.js.map +1 -0
- package/dist/esm/RouterProvider.d.ts +27 -0
- package/dist/esm/config.d.ts +17 -0
- package/dist/esm/config.js +11 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/defer.d.ts +37 -0
- package/dist/esm/defer.js +40 -0
- package/dist/esm/defer.js.map +1 -0
- package/dist/esm/fileRoute.d.ts +24 -0
- package/dist/esm/global.d.ts +7 -0
- package/dist/esm/hash-scroll.d.ts +7 -0
- package/dist/esm/hash-scroll.js +20 -0
- package/dist/esm/hash-scroll.js.map +1 -0
- package/dist/esm/history.d.ts +8 -0
- package/dist/esm/index.d.ts +53 -0
- package/dist/esm/index.js +24 -0
- package/dist/esm/invariant.d.ts +1 -0
- package/dist/esm/invariant.js +8 -0
- package/dist/esm/invariant.js.map +1 -0
- package/dist/esm/isServer/client.d.ts +1 -0
- package/dist/esm/isServer/client.js +6 -0
- package/dist/esm/isServer/client.js.map +1 -0
- package/dist/esm/isServer/development.d.ts +1 -0
- package/dist/esm/isServer/development.js +6 -0
- package/dist/esm/isServer/development.js.map +1 -0
- package/dist/esm/isServer/server.d.ts +1 -0
- package/dist/esm/isServer/server.js +6 -0
- package/dist/esm/isServer/server.js.map +1 -0
- package/dist/esm/link.d.ts +221 -0
- package/dist/esm/link.js +6 -0
- package/dist/esm/link.js.map +1 -0
- package/dist/esm/load-matches.d.ts +18 -0
- package/dist/esm/load-matches.js +657 -0
- package/dist/esm/load-matches.js.map +1 -0
- package/dist/esm/location.d.ts +50 -0
- package/dist/esm/lru-cache.d.ts +6 -0
- package/dist/esm/lru-cache.js +70 -0
- package/dist/esm/lru-cache.js.map +1 -0
- package/dist/esm/manifest.d.ts +35 -0
- package/dist/esm/manifest.js +17 -0
- package/dist/esm/manifest.js.map +1 -0
- package/dist/esm/new-process-route-tree.d.ts +236 -0
- package/dist/esm/new-process-route-tree.js +749 -0
- package/dist/esm/new-process-route-tree.js.map +1 -0
- package/dist/esm/not-found.d.ts +32 -0
- package/dist/esm/not-found.js +25 -0
- package/dist/esm/not-found.js.map +1 -0
- package/dist/esm/path.d.ts +56 -0
- package/dist/esm/path.js +243 -0
- package/dist/esm/path.js.map +1 -0
- package/dist/esm/qss.d.ts +33 -0
- package/dist/esm/qss.js +69 -0
- package/dist/esm/qss.js.map +1 -0
- package/dist/esm/redirect.d.ts +77 -0
- package/dist/esm/redirect.js +53 -0
- package/dist/esm/redirect.js.map +1 -0
- package/dist/esm/rewrite.d.ts +30 -0
- package/dist/esm/rewrite.js +65 -0
- package/dist/esm/rewrite.js.map +1 -0
- package/dist/esm/root.d.ts +3 -0
- package/dist/esm/root.js +7 -0
- package/dist/esm/root.js.map +1 -0
- package/dist/esm/route.d.ts +552 -0
- package/dist/esm/route.js +98 -0
- package/dist/esm/route.js.map +1 -0
- package/dist/esm/routeInfo.d.ts +54 -0
- package/dist/esm/router.d.ts +734 -0
- package/dist/esm/router.js +1165 -0
- package/dist/esm/router.js.map +1 -0
- package/dist/esm/scroll-restoration-inline.d.ts +6 -0
- package/dist/esm/scroll-restoration-inline.js +6 -0
- package/dist/esm/scroll-restoration-inline.js.map +1 -0
- package/dist/esm/scroll-restoration-script/client.d.ts +2 -0
- package/dist/esm/scroll-restoration-script/client.js +8 -0
- package/dist/esm/scroll-restoration-script/client.js.map +1 -0
- package/dist/esm/scroll-restoration-script/server.d.ts +2 -0
- package/dist/esm/scroll-restoration-script/server.js +29 -0
- package/dist/esm/scroll-restoration-script/server.js.map +1 -0
- package/dist/esm/scroll-restoration.d.ts +38 -0
- package/dist/esm/scroll-restoration.js +187 -0
- package/dist/esm/scroll-restoration.js.map +1 -0
- package/dist/esm/searchMiddleware.d.ts +25 -0
- package/dist/esm/searchMiddleware.js +54 -0
- package/dist/esm/searchMiddleware.js.map +1 -0
- package/dist/esm/searchParams.d.ts +31 -0
- package/dist/esm/searchParams.js +62 -0
- package/dist/esm/searchParams.js.map +1 -0
- package/dist/esm/ssr/client.d.ts +6 -0
- package/dist/esm/ssr/client.js +4 -0
- package/dist/esm/ssr/constants.d.ts +3 -0
- package/dist/esm/ssr/constants.js +7 -0
- package/dist/esm/ssr/constants.js.map +1 -0
- package/dist/esm/ssr/createRequestHandler.d.ts +9 -0
- package/dist/esm/ssr/createRequestHandler.js +44 -0
- package/dist/esm/ssr/createRequestHandler.js.map +1 -0
- package/dist/esm/ssr/handlerCallback.d.ts +9 -0
- package/dist/esm/ssr/handlerCallback.js +8 -0
- package/dist/esm/ssr/handlerCallback.js.map +1 -0
- package/dist/esm/ssr/headers.d.ts +3 -0
- package/dist/esm/ssr/headers.js +21 -0
- package/dist/esm/ssr/headers.js.map +1 -0
- package/dist/esm/ssr/json.d.ts +10 -0
- package/dist/esm/ssr/json.js +11 -0
- package/dist/esm/ssr/json.js.map +1 -0
- package/dist/esm/ssr/serializer/RawStream.d.ts +64 -0
- package/dist/esm/ssr/serializer/RawStream.js +282 -0
- package/dist/esm/ssr/serializer/RawStream.js.map +1 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +9 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +33 -0
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
- package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js +13 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
- package/dist/esm/ssr/serializer/transformer.d.ts +91 -0
- package/dist/esm/ssr/serializer/transformer.js +51 -0
- package/dist/esm/ssr/serializer/transformer.js.map +1 -0
- package/dist/esm/ssr/server.d.ts +6 -0
- package/dist/esm/ssr/server.js +5 -0
- package/dist/esm/ssr/ssr-client.d.ts +10 -0
- package/dist/esm/ssr/ssr-client.js +183 -0
- package/dist/esm/ssr/ssr-client.js.map +1 -0
- package/dist/esm/ssr/ssr-match-id.d.ts +2 -0
- package/dist/esm/ssr/ssr-match-id.js +11 -0
- package/dist/esm/ssr/ssr-match-id.js.map +1 -0
- package/dist/esm/ssr/ssr-server.d.ts +42 -0
- package/dist/esm/ssr/ssr-server.js +277 -0
- package/dist/esm/ssr/ssr-server.js.map +1 -0
- package/dist/esm/ssr/transformStreamWithRouter.d.ts +11 -0
- package/dist/esm/ssr/transformStreamWithRouter.js +325 -0
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -0
- package/dist/esm/ssr/tsrScript.d.ts +0 -0
- package/dist/esm/ssr/tsrScript.js +6 -0
- package/dist/esm/ssr/tsrScript.js.map +1 -0
- package/dist/esm/ssr/types.d.ts +30 -0
- package/dist/esm/stores.d.ts +70 -0
- package/dist/esm/stores.js +146 -0
- package/dist/esm/stores.js.map +1 -0
- package/dist/esm/structuralSharing.d.ts +4 -0
- package/dist/esm/typePrimitives.d.ts +65 -0
- package/dist/esm/useLoaderData.d.ts +5 -0
- package/dist/esm/useLoaderDeps.d.ts +5 -0
- package/dist/esm/useNavigate.d.ts +3 -0
- package/dist/esm/useParams.d.ts +5 -0
- package/dist/esm/useRouteContext.d.ts +9 -0
- package/dist/esm/useSearch.d.ts +5 -0
- package/dist/esm/utils.d.ts +178 -0
- package/dist/esm/utils.js +322 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/validators.d.ts +51 -0
- package/package.json +200 -0
- package/skills/router-core/SKILL.md +139 -0
- package/skills/router-core/auth-and-guards/SKILL.md +458 -0
- package/skills/router-core/code-splitting/SKILL.md +322 -0
- package/skills/router-core/data-loading/SKILL.md +485 -0
- package/skills/router-core/navigation/SKILL.md +448 -0
- package/skills/router-core/not-found-and-errors/SKILL.md +435 -0
- package/skills/router-core/path-params/SKILL.md +382 -0
- package/skills/router-core/search-params/SKILL.md +349 -0
- package/skills/router-core/search-params/references/validation-patterns.md +379 -0
- package/skills/router-core/ssr/SKILL.md +437 -0
- package/skills/router-core/type-safety/SKILL.md +497 -0
- package/src/Matches.ts +291 -0
- package/src/RouterProvider.ts +47 -0
- package/src/config.ts +42 -0
- package/src/defer.ts +69 -0
- package/src/fileRoute.ts +164 -0
- package/src/global.ts +9 -0
- package/src/hash-scroll.ts +21 -0
- package/src/history.ts +9 -0
- package/src/index.ts +471 -0
- package/src/invariant.ts +3 -0
- package/src/isServer/client.ts +1 -0
- package/src/isServer/development.ts +2 -0
- package/src/isServer/server.ts +1 -0
- package/src/link.ts +704 -0
- package/src/load-matches.ts +1281 -0
- package/src/location.ts +51 -0
- package/src/lru-cache.ts +74 -0
- package/src/manifest.ts +68 -0
- package/src/new-process-route-tree.ts +1387 -0
- package/src/not-found.ts +41 -0
- package/src/path.ts +436 -0
- package/src/qss.ts +81 -0
- package/src/redirect.ts +179 -0
- package/src/rewrite.ts +93 -0
- package/src/root.ts +3 -0
- package/src/route.ts +2235 -0
- package/src/routeInfo.ts +235 -0
- package/src/router.ts +3207 -0
- package/src/scroll-restoration-inline.ts +81 -0
- package/src/scroll-restoration-script/client.ts +5 -0
- package/src/scroll-restoration-script/server.ts +64 -0
- package/src/scroll-restoration.ts +357 -0
- package/src/searchMiddleware.ts +76 -0
- package/src/searchParams.ts +90 -0
- package/src/ssr/client.ts +6 -0
- package/src/ssr/constants.ts +3 -0
- package/src/ssr/createRequestHandler.ts +98 -0
- package/src/ssr/handlerCallback.ts +15 -0
- package/src/ssr/headers.ts +40 -0
- package/src/ssr/json.ts +16 -0
- package/src/ssr/serializer/RawStream.ts +464 -0
- package/src/ssr/serializer/ShallowErrorPlugin.ts +43 -0
- package/src/ssr/serializer/seroval-plugins.ts +12 -0
- package/src/ssr/serializer/transformer.ts +312 -0
- package/src/ssr/server.ts +14 -0
- package/src/ssr/ssr-client.ts +313 -0
- package/src/ssr/ssr-match-id.ts +7 -0
- package/src/ssr/ssr-server.ts +425 -0
- package/src/ssr/transformStreamWithRouter.ts +493 -0
- package/src/ssr/tsrScript.ts +20 -0
- package/src/ssr/types.ts +41 -0
- package/src/stores.ts +342 -0
- package/src/structuralSharing.ts +7 -0
- package/src/typePrimitives.ts +181 -0
- package/src/useLoaderData.ts +20 -0
- package/src/useLoaderDeps.ts +13 -0
- package/src/useNavigate.ts +13 -0
- package/src/useParams.ts +20 -0
- package/src/useRouteContext.ts +39 -0
- package/src/useSearch.ts +20 -0
- package/src/utils.ts +708 -0
- package/src/validators.ts +121 -0
- package/src/vite-env.d.ts +4 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-core/auth-and-guards
|
|
3
|
+
description: >-
|
|
4
|
+
Route protection with beforeLoad, redirect()/throw redirect(),
|
|
5
|
+
isRedirect helper, authenticated layout routes (_authenticated),
|
|
6
|
+
non-redirect auth (inline login), RBAC with roles and permissions,
|
|
7
|
+
auth provider integration (Auth0, Clerk, Supabase), router context
|
|
8
|
+
for auth state.
|
|
9
|
+
type: sub-skill
|
|
10
|
+
library: tanstack-router
|
|
11
|
+
library_version: '1.166.2'
|
|
12
|
+
requires:
|
|
13
|
+
- router-core
|
|
14
|
+
- router-core/data-loading
|
|
15
|
+
sources:
|
|
16
|
+
- TanStack/router:docs/router/guide/authenticated-routes.md
|
|
17
|
+
- TanStack/router:docs/router/how-to/setup-authentication.md
|
|
18
|
+
- TanStack/router:docs/router/how-to/setup-auth-providers.md
|
|
19
|
+
- TanStack/router:docs/router/how-to/setup-rbac.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Auth and Guards
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
Protect routes with `beforeLoad` + `redirect()` in a pathless layout route (`_authenticated`):
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// src/routes/_authenticated.tsx
|
|
30
|
+
import { createFileRoute, redirect } from '@benjavicente/react-router'
|
|
31
|
+
|
|
32
|
+
export const Route = createFileRoute('/_authenticated')({
|
|
33
|
+
beforeLoad: ({ context, location }) => {
|
|
34
|
+
if (!context.auth.isAuthenticated) {
|
|
35
|
+
throw redirect({
|
|
36
|
+
to: '/login',
|
|
37
|
+
search: {
|
|
38
|
+
redirect: location.href,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
// component defaults to Outlet — no need to declare it
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Any route file placed under `src/routes/_authenticated/` is automatically protected:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// src/routes/_authenticated/dashboard.tsx
|
|
51
|
+
import { createFileRoute } from '@benjavicente/react-router'
|
|
52
|
+
|
|
53
|
+
export const Route = createFileRoute('/_authenticated/dashboard')({
|
|
54
|
+
component: DashboardComponent,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
function DashboardComponent() {
|
|
58
|
+
const { auth } = Route.useRouteContext()
|
|
59
|
+
return <div>Welcome, {auth.user?.username}</div>
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Core Patterns
|
|
64
|
+
|
|
65
|
+
### Router Context for Auth State
|
|
66
|
+
|
|
67
|
+
Auth state flows into the router via `createRootRouteWithContext` and `RouterProvider`'s `context` prop:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// src/routes/__root.tsx
|
|
71
|
+
import { createRootRouteWithContext, Outlet } from '@benjavicente/react-router'
|
|
72
|
+
|
|
73
|
+
interface AuthState {
|
|
74
|
+
isAuthenticated: boolean
|
|
75
|
+
user: { id: string; username: string; email: string } | null
|
|
76
|
+
login: (username: string, password: string) => Promise<void>
|
|
77
|
+
logout: () => void
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface MyRouterContext {
|
|
81
|
+
auth: AuthState
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|
85
|
+
component: () => <Outlet />,
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// src/router.tsx
|
|
91
|
+
import { createRouter } from '@benjavicente/react-router'
|
|
92
|
+
import { routeTree } from './routeTree.gen'
|
|
93
|
+
|
|
94
|
+
export const router = createRouter({
|
|
95
|
+
routeTree,
|
|
96
|
+
context: {
|
|
97
|
+
auth: undefined!, // placeholder — filled by RouterProvider context prop
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
declare module '@benjavicente/react-router' {
|
|
102
|
+
interface Register {
|
|
103
|
+
router: typeof router
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
// src/App.tsx
|
|
110
|
+
import { RouterProvider } from '@benjavicente/react-router'
|
|
111
|
+
import { AuthProvider, useAuth } from './auth'
|
|
112
|
+
import { router } from './router'
|
|
113
|
+
|
|
114
|
+
function InnerApp() {
|
|
115
|
+
const auth = useAuth()
|
|
116
|
+
// context prop injects live auth state WITHOUT recreating the router
|
|
117
|
+
return <RouterProvider router={router} context={{ auth }} />
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function App() {
|
|
121
|
+
return (
|
|
122
|
+
<AuthProvider>
|
|
123
|
+
<InnerApp />
|
|
124
|
+
</AuthProvider>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The router is created once with a placeholder. `RouterProvider`'s `context` prop injects the live auth state on each render — this avoids recreating the router on auth changes (which would reset caches and rebuild the route tree).
|
|
130
|
+
|
|
131
|
+
### Redirect-Based Auth with Redirect-Back
|
|
132
|
+
|
|
133
|
+
Save the current location in search params so you can redirect back after login:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
// src/routes/_authenticated.tsx
|
|
137
|
+
import { createFileRoute, redirect } from '@benjavicente/react-router'
|
|
138
|
+
|
|
139
|
+
export const Route = createFileRoute('/_authenticated')({
|
|
140
|
+
beforeLoad: ({ context, location }) => {
|
|
141
|
+
if (!context.auth.isAuthenticated) {
|
|
142
|
+
throw redirect({
|
|
143
|
+
to: '/login',
|
|
144
|
+
search: { redirect: location.href },
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
// src/routes/login.tsx
|
|
153
|
+
import { createFileRoute, redirect } from '@benjavicente/react-router'
|
|
154
|
+
import { useState, type FormEvent } from 'react'
|
|
155
|
+
|
|
156
|
+
// Validate redirect target to prevent open redirect attacks
|
|
157
|
+
function sanitizeRedirect(url: unknown): string {
|
|
158
|
+
if (typeof url !== 'string' || !url.startsWith('/') || url.startsWith('//')) {
|
|
159
|
+
return '/'
|
|
160
|
+
}
|
|
161
|
+
return url
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const Route = createFileRoute('/login')({
|
|
165
|
+
validateSearch: (search) => ({
|
|
166
|
+
redirect: sanitizeRedirect(search.redirect),
|
|
167
|
+
}),
|
|
168
|
+
beforeLoad: ({ context, search }) => {
|
|
169
|
+
if (context.auth.isAuthenticated) {
|
|
170
|
+
throw redirect({ to: search.redirect })
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
component: LoginComponent,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
function LoginComponent() {
|
|
177
|
+
const { auth } = Route.useRouteContext()
|
|
178
|
+
const search = Route.useSearch()
|
|
179
|
+
const navigate = Route.useNavigate()
|
|
180
|
+
const [username, setUsername] = useState('')
|
|
181
|
+
const [password, setPassword] = useState('')
|
|
182
|
+
const [error, setError] = useState('')
|
|
183
|
+
|
|
184
|
+
const handleSubmit = async (e: FormEvent) => {
|
|
185
|
+
e.preventDefault()
|
|
186
|
+
try {
|
|
187
|
+
await auth.login(username, password)
|
|
188
|
+
navigate({ to: search.redirect })
|
|
189
|
+
} catch {
|
|
190
|
+
setError('Invalid credentials')
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<form onSubmit={handleSubmit}>
|
|
196
|
+
{error && <div>{error}</div>}
|
|
197
|
+
<input value={username} onChange={(e) => setUsername(e.target.value)} />
|
|
198
|
+
<input
|
|
199
|
+
type="password"
|
|
200
|
+
value={password}
|
|
201
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
202
|
+
/>
|
|
203
|
+
<button type="submit">Sign In</button>
|
|
204
|
+
</form>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Non-Redirect Auth (Inline Login)
|
|
210
|
+
|
|
211
|
+
Instead of redirecting, show a login form in place of the `Outlet`:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
// src/routes/_authenticated.tsx
|
|
215
|
+
import { createFileRoute, Outlet } from '@benjavicente/react-router'
|
|
216
|
+
|
|
217
|
+
export const Route = createFileRoute('/_authenticated')({
|
|
218
|
+
component: AuthenticatedLayout,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
function AuthenticatedLayout() {
|
|
222
|
+
const { auth } = Route.useRouteContext()
|
|
223
|
+
|
|
224
|
+
if (!auth.isAuthenticated) {
|
|
225
|
+
return <LoginForm />
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return <Outlet />
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
This keeps the URL unchanged — the user stays on the same page and sees a login form instead of protected content. After authentication, `<Outlet />` renders and child routes appear.
|
|
233
|
+
|
|
234
|
+
### RBAC with Roles and Permissions
|
|
235
|
+
|
|
236
|
+
Extend auth state with role/permission helpers, then check in `beforeLoad`:
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
// src/auth.tsx
|
|
240
|
+
interface User {
|
|
241
|
+
id: string
|
|
242
|
+
username: string
|
|
243
|
+
email: string
|
|
244
|
+
roles: string[]
|
|
245
|
+
permissions: string[]
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface AuthState {
|
|
249
|
+
isAuthenticated: boolean
|
|
250
|
+
user: User | null
|
|
251
|
+
hasRole: (role: string) => boolean
|
|
252
|
+
hasAnyRole: (roles: string[]) => boolean
|
|
253
|
+
hasPermission: (permission: string) => boolean
|
|
254
|
+
hasAnyPermission: (permissions: string[]) => boolean
|
|
255
|
+
login: (username: string, password: string) => Promise<void>
|
|
256
|
+
logout: () => void
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Admin-only layout route:
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
// src/routes/_authenticated/_admin.tsx
|
|
264
|
+
import { createFileRoute, redirect } from '@benjavicente/react-router'
|
|
265
|
+
|
|
266
|
+
export const Route = createFileRoute('/_authenticated/_admin')({
|
|
267
|
+
beforeLoad: ({ context, location }) => {
|
|
268
|
+
if (!context.auth.hasRole('admin')) {
|
|
269
|
+
throw redirect({
|
|
270
|
+
to: '/unauthorized',
|
|
271
|
+
search: { redirect: location.href },
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Multi-role access:
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
// src/routes/_authenticated/_moderator.tsx
|
|
282
|
+
import { createFileRoute, redirect } from '@benjavicente/react-router'
|
|
283
|
+
|
|
284
|
+
export const Route = createFileRoute('/_authenticated/_moderator')({
|
|
285
|
+
beforeLoad: ({ context, location }) => {
|
|
286
|
+
if (!context.auth.hasAnyRole(['admin', 'moderator'])) {
|
|
287
|
+
throw redirect({
|
|
288
|
+
to: '/unauthorized',
|
|
289
|
+
search: { redirect: location.href },
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Permission-based:
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
// src/routes/_authenticated/_users.tsx
|
|
300
|
+
import { createFileRoute, redirect } from '@benjavicente/react-router'
|
|
301
|
+
|
|
302
|
+
export const Route = createFileRoute('/_authenticated/_users')({
|
|
303
|
+
beforeLoad: ({ context, location }) => {
|
|
304
|
+
if (!context.auth.hasAnyPermission(['users:read', 'users:write'])) {
|
|
305
|
+
throw redirect({
|
|
306
|
+
to: '/unauthorized',
|
|
307
|
+
search: { redirect: location.href },
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Page-level permission check (nested under an already-role-protected layout):
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
// src/routes/_authenticated/_users/manage.tsx
|
|
318
|
+
import { createFileRoute } from '@benjavicente/react-router'
|
|
319
|
+
|
|
320
|
+
export const Route = createFileRoute('/_authenticated/_users/manage')({
|
|
321
|
+
beforeLoad: ({ context }) => {
|
|
322
|
+
if (!context.auth.hasPermission('users:write')) {
|
|
323
|
+
throw new Error('Write permission required')
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
component: UserManagement,
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
function UserManagement() {
|
|
330
|
+
const { auth } = Route.useRouteContext()
|
|
331
|
+
const canDelete = auth.hasPermission('users:delete')
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<div>
|
|
335
|
+
<h1>User Management</h1>
|
|
336
|
+
{canDelete && <button>Delete User</button>}
|
|
337
|
+
</div>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Handling Auth Check Failures (isRedirect)
|
|
343
|
+
|
|
344
|
+
When `beforeLoad` has a try/catch, redirects (which work by throwing) can get swallowed. Use `isRedirect` to re-throw:
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
import { createFileRoute, redirect, isRedirect } from '@benjavicente/react-router'
|
|
348
|
+
|
|
349
|
+
export const Route = createFileRoute('/_authenticated')({
|
|
350
|
+
beforeLoad: async ({ context, location }) => {
|
|
351
|
+
try {
|
|
352
|
+
const user = await verifySession(context.auth)
|
|
353
|
+
if (!user) {
|
|
354
|
+
throw redirect({
|
|
355
|
+
to: '/login',
|
|
356
|
+
search: { redirect: location.href },
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
return { user }
|
|
360
|
+
} catch (error) {
|
|
361
|
+
if (isRedirect(error)) throw error // re-throw redirect, don't swallow it
|
|
362
|
+
// Actual error — redirect to login
|
|
363
|
+
throw redirect({
|
|
364
|
+
to: '/login',
|
|
365
|
+
search: { redirect: location.href },
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
})
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Common Mistakes
|
|
373
|
+
|
|
374
|
+
### HIGH: Auth check in component instead of beforeLoad
|
|
375
|
+
|
|
376
|
+
Component-level auth checks cause a **flash of protected content** before the redirect:
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
// WRONG — protected content renders briefly before redirect
|
|
380
|
+
export const Route = createFileRoute('/_authenticated/dashboard')({
|
|
381
|
+
component: () => {
|
|
382
|
+
const auth = useAuth()
|
|
383
|
+
if (!auth.isAuthenticated) return <Navigate to="/login" />
|
|
384
|
+
return <Dashboard />
|
|
385
|
+
},
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// CORRECT — beforeLoad runs before any rendering
|
|
389
|
+
export const Route = createFileRoute('/_authenticated/dashboard')({
|
|
390
|
+
beforeLoad: ({ context }) => {
|
|
391
|
+
if (!context.auth.isAuthenticated) {
|
|
392
|
+
throw redirect({ to: '/login' })
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
component: Dashboard,
|
|
396
|
+
})
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`beforeLoad` runs before any component rendering and before the loader. It completely prevents the flash.
|
|
400
|
+
|
|
401
|
+
### HIGH: Not re-throwing redirects in try/catch
|
|
402
|
+
|
|
403
|
+
`redirect()` works by throwing. If `beforeLoad` has a try/catch, the redirect gets swallowed:
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
// WRONG — redirect is caught and swallowed
|
|
407
|
+
beforeLoad: async ({ context }) => {
|
|
408
|
+
try {
|
|
409
|
+
await validateSession(context.auth)
|
|
410
|
+
} catch (e) {
|
|
411
|
+
console.error(e) // swallows the redirect!
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// CORRECT — use isRedirect to distinguish intentional redirects from errors
|
|
416
|
+
import { isRedirect } from '@benjavicente/react-router'
|
|
417
|
+
|
|
418
|
+
beforeLoad: async ({ context }) => {
|
|
419
|
+
try {
|
|
420
|
+
await validateSession(context.auth)
|
|
421
|
+
} catch (e) {
|
|
422
|
+
if (isRedirect(e)) throw e
|
|
423
|
+
console.error(e)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### MEDIUM: Conditionally rendering root route component
|
|
429
|
+
|
|
430
|
+
The root route always renders regardless of auth state. You cannot conditionally render its component:
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
// WRONG — root route always renders, this doesn't protect anything
|
|
434
|
+
export const Route = createRootRoute({
|
|
435
|
+
component: () => {
|
|
436
|
+
if (!isAuthenticated()) return <Login />
|
|
437
|
+
return <Outlet />
|
|
438
|
+
},
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// CORRECT — use a pathless layout route for auth boundaries
|
|
442
|
+
// src/routes/_authenticated.tsx
|
|
443
|
+
export const Route = createFileRoute('/_authenticated')({
|
|
444
|
+
beforeLoad: ({ context }) => {
|
|
445
|
+
if (!context.auth.isAuthenticated) {
|
|
446
|
+
throw redirect({ to: '/login' })
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
})
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Place protected routes as children of the `_authenticated` layout route. Public routes (login, home, etc.) live outside it.
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Cross-References
|
|
457
|
+
|
|
458
|
+
- See also: **router-core/data-loading/SKILL.md** — `beforeLoad` runs before `loader`; auth context flows into loader via route context
|