clerk-react-router-patterns

Par clerk · skills

'Patterns React Router v7/v8 avec Clerk — rootAuthLoader, getAuth dans les loaders,

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

Motifs React Router

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

De quoi avez-vous besoin ?

Tâche Référence
Auth dans les loaders et actions references/loaders-actions.md
Routes protégées et redirections references/protected-routes.md
Données utilisateur SSR et session references/ssr-auth.md

React Router v7 vs v8

Vérifiez la version majeure de react-router installée avant de configurer — la configuration diffère :

v7.9+ v8+
API Middleware Opt-in : définissez future: { v8_middleware: true } dans react-router.config.ts Toujours activé — NE définissez PAS le flag (v8 l'a supprimé)
Contournement ssr.noExternal (ci-dessous) Non nécessaire Requis

Configuration minimale

1. vite.config.ts (v8 uniquement — OBLIGATOIRE)

React Router v8 fournit des exports conditionnels développement/production. Dans react-router dev, Vite externalise @clerk/react-router pour SSR, donc Node résout la version production de react-router tandis que le code app obtient la version développement — deux instances de module, deux contextes Router. Chaque requête échoue alors lors de SSR avec :

Error: useNavigate() may be used only in the context of a <Router> component.

npm ls react-router affiche une seule copie — cela N'EXCLUT PAS cela. La duplication est par condition d'export, pas par copie installée. Ne cherchez pas les installations en double ; ajoutez le contournement (problème upstream : 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>
  )
}

Il n'y a pas de HOC ClerkApp dans @clerk/react-router (c'était l'API @clerk/remix). Rendez <ClerkProvider loaderData={loaderData}> à l'intérieur de l'export par défaut et passez-lui les loaderData de la route root.

3. react-router.config.ts (v7 uniquement)

import type { Config } from '@react-router/dev/config'

export default {
  future: {
    v8_middleware: true,
  },
} satisfies Config

Sur v8, omettez le bloc future entièrement — le flag n'existe plus.

Requis : rootAuthLoader doit être appelé dans le loader de root.tsx. Sans cela, getAuth lance une erreur dans les loaders imbriqués.

Modèle mental

React Router v7/v8 utilise un pipeline middleware + loader. Clerk se connecte aux deux couches :

  • Middleware (clerkMiddleware()) — s'exécute à chaque requête, attache l'auth au contexte
  • rootAuthLoader — requis dans root.tsx pour passer l'état Clerk au client
  • getAuth(args) — appelé à l'intérieur de tout loader/action pour obtenir l'utilisateur courant
Requête → clerkMiddleware() → rootAuthLoader → page loader → composant
             ↓                   ↓               ↓
       attache l'auth      injecte l'état     getAuth(args)
       au contexte         à la réponse       lit le contexte

Auth dans les 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 dans les 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')
}

Composants Client

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>
}

Changement d'organisation

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) }
}

Pièges courants

Symptôme Cause Solution
useNavigate() may be used only in the context of a <Router> lancé par ClerkProvider lors de SSR en dev (v8) Vite dev SSR externalise @clerk/react-router, qui charge ensuite la version production de react-router tandis que l'app utilise la version développement — deux contextes Router. Une seule copie dans npm ls n'exclut pas cela. Ajoutez ssr: { noExternal: ['@clerk/react-router'] } à vite.config.ts. NE rétrograder PAS vers v7
Erreur de build : ClerkApp is not exported ClerkApp n'existe pas dans @clerk/react-router Utilisez <ClerkProvider loaderData={loaderData}> dans l'export par défaut de root.tsx
clerkMiddleware() not detected Middleware manquant (ou sur v7, flag future v8_middleware manquant) Exportez middleware = [clerkMiddleware()] depuis la route root ; sur v7 définissez aussi future: { v8_middleware: true }
Erreur/avertissement flag future inconnu (v8) Flag v8_middleware laissé dans react-router.config.ts après upgrade Supprimez l'entrée future.v8_middleware — le middleware est toujours activé dans v8
getAuth retourne un userId vide rootAuthLoader non appelé Appelez rootAuthLoader(args) dans le loader de root.tsx
Boucle de redirection infinie La cible de redirection est aussi protégée Excluez /sign-in de la vérification de protection
redirect ne fonctionne pas dans action Utilisation de Response au lieu de throw redirect() Utilisez throw redirect('/path') depuis react-router

Carte d'imports

Quoi Importer depuis
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

Voir aussi

  • clerk-setup - Installation initiale de Clerk
  • clerk-custom-ui - Flux personnalisés et apparence
  • clerk-orgs - Organisations B2B

Docs

SDK React Router

Skills similaires