clerk-react-router-patterns
React Router v7/v8 patterns with Clerk — rootAuthLoader, getAuth in loaders, clerkMiddleware, protected routes, SSR user data, org switching. Triggers on: react-router auth, rootAuthLoader, getAuth loader, react-router protected route, loader authentication, SSR auth react-router, useNavigate may be used only in the context of a Router.
- License 许可
- MIT
- 最近更新
- 2026-11-13
- Stars
- ★ 1
- Forks
- 0
01 前言 Front Matter
- name
- clerk-react-router-patterns
- license
- MIT
- metadata
- { "author": "clerk", "version": "1.1.0" }
- description
- React Router v7/v8 patterns with Clerk — rootAuthLoader, getAuth in loaders, clerkMiddleware, protected routes, SSR user data, org switching. Triggers on: react-router auth, rootAuthLoader, getAuth loader, react-router protected route, loader authentication, SSR auth react-router, useNavigate may be used only in the context of a Router.
- allowed-tools
- WebFetch
02 资源文件 16
- 参考文档evals/evals.json4.6 KB
- 参考文档references/design-system.md12.9 KB
- 参考文档references/diagram-shell.md4.0 KB
- 参考文档references/loaders-actions.md1.7 KB
- 参考文档references/pr-components.md15.6 KB
- 参考文档references/protected-routes.md1.7 KB
- 参考文档references/ssr-auth.md1.4 KB
- 参考文档references/svg-patterns.md13.2 KB
- 参考文档references/theme-override.md7.8 KB
- 其他templates/react-router-basic-auth/app/app.css—
- 其他templates/react-router-basic-auth/app/root.tsx2.4 KB
- 脚本templates/react-router-basic-auth/app/routes.ts132 B
- 其他templates/react-router-basic-auth/app/routes/home.tsx57 B
- 参考文档templates/react-router-basic-auth/package.json427 B
- 脚本templates/react-router-basic-auth/react-router.config.ts105 B
- 脚本templates/react-router-basic-auth/vite.config.ts499 B
03 SKILL.md 正文
React Router Patterns
SDK: @clerk/react-router v3.5+. Supports React Router v7.9+ and v8.
What Do You Need?
| Task | Reference |
|---|---|
| Auth in loaders and actions | references/loaders-actions.md |
| Protected routes and redirects | references/protected-routes.md |
| SSR user data and session | references/ssr-auth.md |
React Router v7 vs v8
Check the installed react-router major version before scaffolding — the config differs:
| v7.9+ | v8+ | |
|---|---|---|
| Middleware API | Opt-in: set future: { v8_middleware: true } in react-router.config.ts |
Always on — do NOT set the flag (v8 removed it) |
ssr.noExternal workaround (below) |
Not needed | Required |
Minimal Setup
1. vite.config.ts (v8 only — REQUIRED)
React Router v8 ships development/production conditional exports. In react-router dev,
Vite externalizes @clerk/react-router for SSR, so Node resolves the production build of
react-router while the app code gets the development build — two module instances, two
Router contexts. Every request then fails during SSR with:
Error: useNavigate() may be used only in the context of a <Router> component.
npm ls react-router shows a single copy — that does NOT rule this out. The
duplication is per export condition, not per installed copy. Do not chase duplicate
installs; add the workaround (upstream issue:
https://github.com/remix-run/react-router/issues/15232):
import { reactRouter } from '@react-router/dev/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [reactRouter()],
ssr: {
noExternal: ['@clerk/react-router'],
},
})
2. root.tsx
import { Outlet } from 'react-router'
import { rootAuthLoader, clerkMiddleware } from '@clerk/react-router/server'
import { ClerkProvider } from '@clerk/react-router'
import type { Route } from './+types/root'
export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()]
export async function loader(args: Route.LoaderArgs) {
return rootAuthLoader(args)
}
export default function App({ loaderData }: Route.ComponentProps) {
return (
<ClerkProvider loaderData={loaderData}>
<Outlet />
</ClerkProvider>
)
}
There is no ClerkApp HOC in @clerk/react-router (that was the @clerk/remix API).
Render <ClerkProvider loaderData={loaderData}> inside the default export and pass it
the root route's loaderData.
3. react-router.config.ts (v7 only)
import type { Config } from '@react-router/dev/config'
export default {
future: {
v8_middleware: true,
},
} satisfies Config
On v8, omit the future block entirely — the flag no longer exists.
Required:
rootAuthLoadermust be called inroot.tsx's loader. Without it,getAuththrows in nested loaders.
Mental Model
React Router v7/v8 uses a middleware + loader pipeline. Clerk plugs into both layers:
- Middleware (
clerkMiddleware()) — runs on every request, attaches auth to context rootAuthLoader— required inroot.tsxto pass Clerk state to the clientgetAuth(args)— called inside any loader/action to get the current user
Request → clerkMiddleware() → rootAuthLoader → page loader → component
↓ ↓ ↓
attaches auth injects state getAuth(args)
to context to response reads context
Auth in Loaders
import { getAuth } from '@clerk/react-router/server'
import type { Route } from './+types/dashboard'
export async function loader(args: Route.LoaderArgs) {
const { userId } = await getAuth(args)
if (!userId) throw redirect('/sign-in')
const data = await fetchUserData(userId)
return { data }
}
Auth in Actions
import { getAuth } from '@clerk/react-router/server'
export async function action(args: Route.ActionArgs) {
const { userId, orgId } = await getAuth(args)
if (!userId) throw new Response('Unauthorized', { status: 401 })
const formData = await args.request.formData()
await saveData(userId, orgId, formData)
return redirect('/dashboard')
}
Client Components
import { useAuth, useUser } from '@clerk/react-router'
export function Profile() {
const { userId, isSignedIn } = useAuth()
const { user } = useUser()
if (!isSignedIn) return null
return <p>{user?.firstName}</p>
}
Org Switching
import { OrganizationSwitcher } from '@clerk/react-router'
export function Nav() {
return <OrganizationSwitcher afterSelectOrganizationUrl="/dashboard" />
}
export async function loader(args: Route.LoaderArgs) {
const { userId, orgId } = await getAuth(args)
if (!userId) throw redirect('/sign-in')
if (!orgId) throw redirect('/select-org')
return { data: await fetchOrgData(orgId) }
}
Common Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
useNavigate() may be used only in the context of a <Router> thrown from ClerkProvider during SSR in dev (v8) |
Vite dev SSR externalizes @clerk/react-router, which then loads react-router's production build while the app uses the development build — two Router contexts. A single copy in npm ls does not rule this out. |
Add ssr: { noExternal: ['@clerk/react-router'] } to vite.config.ts. Do NOT downgrade to v7 |
Build error: ClerkApp is not exported |
ClerkApp does not exist in @clerk/react-router |
Use <ClerkProvider loaderData={loaderData}> in root.tsx's default export |
clerkMiddleware() not detected |
Missing middleware (or on v7, missing v8_middleware future flag) |
Export middleware = [clerkMiddleware()] from root route; on v7 also set future: { v8_middleware: true } |
| Unknown future flag error/warning (v8) | v8_middleware flag left in react-router.config.ts after upgrading |
Remove the future.v8_middleware entry — middleware is always on in v8 |
getAuth returns empty userId |
rootAuthLoader not called |
Call rootAuthLoader(args) in root.tsx loader |
| Infinite redirect loop | Redirect target is also protected | Exclude /sign-in from protection check |
redirect not working in action |
Using Response instead of throw redirect() |
Use throw redirect('/path') from react-router |
Import Map
| What | Import From |
|---|---|
getAuth |
@clerk/react-router/server |
rootAuthLoader |
@clerk/react-router/server |
clerkMiddleware |
@clerk/react-router/server |
ClerkProvider |
@clerk/react-router |
useAuth, useUser |
@clerk/react-router |
OrganizationSwitcher |
@clerk/react-router |
See Also
clerk-setup- Initial Clerk installclerk-custom-ui- Custom flows & appearanceclerk-orgs- B2B organizations
Docs
04 同仓库更多 Skills
jev
Ask typed System One judgments (noul / choice / score) against Allternit's LOCAL System One server instead of prompting a chat model to "return JSON". Use when classifying, routing, scoring, or screening something and code needs a probability it can branch on, or when the user runs /jev. Official TypeSafe Jev is an optional, explicit remote lane.
jev-route
Route with local System One answers — map Choice + confidence to act / confirm / human lanes, or pick a model tier by Choice label. Also documents the separate `route-model` helper for OpenRouter's typesafe/jev-router (a chat-model router, NOT System One). Use for confidence routing, model-tier routing, or when the user runs /jev-route.
jev-guardrail
Screen text or agent tool calls with a battery of local System One Noul hazards plus a Score, with policy in code — and the Claude Code PreToolUse guard (hooks/pretooluse-guard) that puts deterministic hard rules first and lets Jev only raise friction. Use for injection/secret/destructive-action screening or when the user runs /jev-guardrail.
jev-fanout
Ask every independent System One question over the same state in ONE POST /v1/systemone to the local server (speculative fan-out). Use for parallel judgments, compound commands, one-call turn assessment, or when the user runs /jev-fanout. Do not fire one HTTP call per question.
memory-drive
Read and write the user's Allternit Memory Drive, a git repo of markdown memories in the open Agent Memory Repo format. Use at the start of a session to load what the user's agents remember (MEMORY.md), when the user says "remember this", when you learn a durable fact about the user or their work, and before answering questions that depend on past sessions.
agent-orchestrator
Orchestrate external CLI agents (kimi, codex, agy, claude, grok, gizzi) through the Allternit Factory (gizzi agents / orchestration / workspace / workflows). Use when the user asks to delegate, offload, or farm out implementation work to another CLI agent / terminal session, or to run work outside this session to save tokens. Part of Allternit Agents / Bot Agents — not a separate product.