Loading...
Loading...
React Router v7 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.
npx skill4agent add clerk/skills clerk-react-router-patterns@clerk/react-router| 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 |
clerkMiddleware()rootAuthLoaderroot.tsxgetAuth(args)Request → clerkMiddleware() → rootAuthLoader → page loader → component
↓ ↓ ↓
attaches auth injects state getAuth(args)
to context to response reads contextimport { rootAuthLoader } from '@clerk/react-router/server'
import { ClerkApp } from '@clerk/react-router'
import type { Route } from './+types/root'
export async function loader(args: Route.LoaderArgs) {
return rootAuthLoader(args)
}
export default ClerkApp(function App() {
return <Outlet />
})import { clerkMiddleware } from '@clerk/react-router/server'
export const middleware = [clerkMiddleware()]Required:must be called inrootAuthLoader's loader. Without it,root.tsxthrows in nested loaders.getAuth
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 }
}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')
}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>
}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) }
}| Symptom | Cause | Fix |
|---|---|---|
| Missing middleware | Export |
| | Call |
| Infinite redirect loop | Redirect target is also protected | Exclude |
| Using | Use |
| What | Import From |
|---|---|
| |
| |
| |
| |
| |
| |
clerk-setupclerk-custom-uiclerk-orgs