clerk-react-router-patterns

작성자: clerk

React Router v7 패턴과 Clerk — rootAuthLoader, 로더에서 getAuth 사용

npx skills add https://github.com/clerk/skills --skill clerk-react-router-patterns

React Router Patterns

SDK: @clerk/react-router v3.5+. Supports React Router v7.9+ and v8.

What Do You Need?

TaskReference
Auth in loaders and actionsreferences/loaders-actions.md
Protected routes and redirectsreferences/protected-routes.md
SSR user data and sessionreferences/ssr-auth.md

React Router v7 vs v8

Check the installed react-router major version before scaffolding — the config differs:

v7.9+v8+
Middleware APIOpt-in: set future: { v8_middleware: true } in react-router.config.tsAlways on — do NOT set the flag (v8 removed it)
ssr.noExternal workaround (below)Not neededRequired

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: rootAuthLoader must be called in root.tsx's loader. Without it, getAuth throws 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 in root.tsx to pass Clerk state to the client
  • getAuth(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

SymptomCauseFix
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 exportedClerkApp does not exist in @clerk/react-routerUse <ClerkProvider loaderData={loaderData}> in root.tsx's default export
clerkMiddleware() not detectedMissing 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 upgradingRemove the future.v8_middleware entry — middleware is always on in v8
getAuth returns empty userIdrootAuthLoader not calledCall rootAuthLoader(args) in root.tsx loader
Infinite redirect loopRedirect target is also protectedExclude /sign-in from protection check
redirect not working in actionUsing Response instead of throw redirect()Use throw redirect('/path') from react-router

Import Map

WhatImport 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 install
  • clerk-custom-ui - Custom flows & appearance
  • clerk-orgs - B2B organizations

Docs

React Router SDK

clerk의 다른 스킬

clerk-monorepo
clerk
Work effectively in the clerk/javascript SDK monorepo. Use when setting up the repo, building / testing / running a package, deciding which of the @clerk/*…
official
mosaic
clerk
Work on Mosaic UI: styling a component with slot recipes (`defineSlotRecipe` / `useRecipe` / slots / variants), or building a flow — authoring a state machine…
official
mosaic-machine
clerk
Mosaic 상태 머신을 작성하고 사용합니다. 사용자가 createMachine으로 상태 머신을 작성하거나, 다단계 흐름을 모델링하거나, 머신을 React에 연결할 때 사용합니다.
official
clerk-android
clerk
Implement Clerk authentication for native Android apps using Kotlin and Jetpack Compose with clerk-android source-guided patterns. Use for prebuilt…
official
clerk-astro-patterns
clerk
Astro 패턴과 Clerk — 미들웨어, SSR 페이지, 아일랜드 컴포넌트, API 라우트, 정적 렌더링 vs SSR 렌더링. 다음에서 트리거: astro clerk, clerk astro middleware,…
official
clerk-backend-api
clerk
Clerk 백엔드 REST API 탐색기 및 실행기. 태그를 탐색하고, 엔드포인트 스키마를 검사하며, 인증된 요청을 실행합니다. 사용자 목록 조회, 관리 시 사용하세요…
official
clerk-chrome-extension-patterns
clerk
Chrome 확장 프로그램 인증 with @clerk/chrome-extension -- 팝업/사이드패널 설정, 웹 앱을 통한 OAuth/SAML용 syncHost, 서비스 워커용 createClerkClient 및…
official
clerk-cli
clerk
Operate the Clerk CLI (`clerk` binary) for authentication, user/org/session management, impersonation, local webhook testing, deploy verification, instance…
official