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 :
rootAuthLoaderdoit être appelé dans le loader deroot.tsx. Sans cela,getAuthlance 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 dansroot.tsxpour passer l'état Clerk au clientgetAuth(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 Clerkclerk-custom-ui- Flux personnalisés et apparenceclerk-orgs- Organisations B2B