Skip to main content
Docs

React Router Quickstart

React Router can be used in different modes: declarative, data, or framework. This tutorial explains how to use React Router in framework mode. To use React Router in declarative mode instead, see the dedicated guide.

This tutorial assumes that you're using React Router v7.1.2 or later in framework mode.

Create a new React app using React Router

If you don't already have a React app using React Router, run the following commands to create a new one.

terminal
npm create react-router@latest clerk-react-router
cd clerk-react-router
npm install
terminal
pnpm create react-router clerk-react-router
cd clerk-react-router
pnpm install
terminal
yarn create react-router clerk-react-router
cd clerk-react-router
yarn install
terminal
bunx create-react-router clerk-react-router
cd clerk-react-router
bun install

Install @clerk/react-router

The Clerk React Router SDK gives you access to prebuilt components, hooks, and helpers to make user authentication easier.

Run the following command to install the SDK:

terminal
npm install @clerk/react-router
terminal
pnpm add @clerk/react-router
terminal
yarn add @clerk/react-router
terminal
bun add @clerk/react-router
.env
VITE_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
CLERK_SECRET_KEY=YOUR_SECRET_KEY

Add clerkMiddleware() and rootAuthLoader() to your app

clerkMiddleware() grants you access to user authentication state throughout your app. It also allows you to protect specific routes from unauthenticated users. To add clerkMiddleware() to your app, follow these steps:

  1. Since React Router middleware requires opting in via a future flag, add the following to your react-router.config.ts file:
react-router.config.ts
import type { Config } from '@react-router/dev/config'

export default {
  // ...
  future: {
    v8_middleware: true,
  },
} satisfies Config
  1. Add the following code to your root.tsx file to configure the clerkMiddleware() and rootAuthLoader() functions.
app/root.tsx
3 lines collapsedimport { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router' import type { Route } from './+types/root' import stylesheet from './app.css?url'
import { clerkMiddleware, rootAuthLoader } from '@clerk/react-router/server' export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()] export const loader = (args: Route.LoaderArgs) => rootAuthLoader(args)
62 lines collapsedexport const links: Route.LinksFunction = () => [ { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous', }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', }, { rel: 'stylesheet', href: stylesheet }, ] export function Layout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> </head> <body> {children} <ScrollRestoration /> <Scripts /> </body> </html> ) } export default function App() { return <Outlet /> } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { let message = 'Oops!' let details = 'An unexpected error occurred.' let stack: string | undefined if (isRouteErrorResponse(error)) { message = error.status === 404 ? '404' : 'Error' details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details } else if (import.meta.env.DEV && error && error instanceof Error) { details = error.message stack = error.stack } return ( <main className="pt-16 p-4 container mx-auto"> <h1>{message}</h1> <p>{details}</p> {stack && ( <pre className="w-full p-4 overflow-x-auto"> <code>{stack}</code> </pre> )} </main> ) }
  1. By default, clerkMiddleware() will not protect any routes. All routes are public and you must opt-in to protection for routes. See the clerkMiddleware() reference to learn how to require authentication for specific routes.

Add <ClerkProvider> to your app

The <ClerkProvider> component provides session and user context to Clerk's hooks and components. It's recommended to wrap your entire app at the entry point with <ClerkProvider> to make authentication globally accessible. See the reference docs for other configuration options.

Pass loaderData to the <ClerkProvider> component, as shown in the following example. This data is provided by the rootAuthLoader() function.

app/root.tsx
import { ClerkProvider } from '@clerk/react-router'
41 lines collapsedimport { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router' import { clerkMiddleware, rootAuthLoader } from '@clerk/react-router/server' import type { Route } from './+types/root' import stylesheet from './app.css?url' export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()] export const loader = (args: Route.LoaderArgs) => rootAuthLoader(args) export const links: Route.LinksFunction = () => [ { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous', }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', }, { rel: 'stylesheet', href: stylesheet }, ] export function Layout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> </head> <body> {children} <ScrollRestoration /> <Scripts /> </body> </html> ) }
// Pull in the `loaderData` from the `rootAuthLoader()` function export default function App({ loaderData }: Route.ComponentProps) { return ( // Pass the `loaderData` to the `<ClerkProvider>` component <ClerkProvider loaderData={loaderData}> <Outlet /> </ClerkProvider> ) }
34 lines collapsedexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { let message = 'Oops!' let details = 'An unexpected error occurred.' let stack: string | undefined if (isRouteErrorResponse(error)) { message = error.status === 404 ? '404' : 'Error' details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details } else if (import.meta.env.DEV && error && error instanceof Error) { details = error.message stack = error.stack } return ( <main className="pt-16 p-4 container mx-auto"> <h1>{message}</h1> <p>{details}</p> {stack && ( <pre className="w-full p-4 overflow-x-auto"> <code>{stack}</code> </pre> )} </main> ) }

Create a header with Clerk components

You can control which content signed-in and signed-out users can see with the prebuilt control components. The following example creates a header using the following components:

app/root.tsx
import {
  ClerkProvider,
  SignedIn,
  SignedOut,
  UserButton,
  SignInButton,
  SignUpButton,
} from '@clerk/react-router'
41 lines collapsedimport { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router' import { clerkMiddleware, rootAuthLoader } from '@clerk/react-router/server' import type { Route } from './+types/root' import stylesheet from './app.css?url' export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()] export const loader = (args: Route.LoaderArgs) => rootAuthLoader(args) export const links: Route.LinksFunction = () => [ { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous', }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', }, { rel: 'stylesheet', href: stylesheet }, ] export function Layout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> </head> <body> {children} <ScrollRestoration /> <Scripts /> </body> </html> ) }
// Pull in the `loaderData` from the `rootAuthLoader()` function export default function App({ loaderData }: Route.ComponentProps) { return ( // Pass the `loaderData` to the `<ClerkProvider>` component <ClerkProvider loaderData={loaderData}> <header className="flex items-center justify-center py-8 px-4"> {/* Show the sign-in and sign-up buttons when the user is signed out */} <SignedOut> <SignInButton /> <SignUpButton /> </SignedOut> {/* Show the user button when the user is signed in */} <SignedIn> <UserButton /> </SignedIn> </header> <Outlet /> </ClerkProvider> ) }
26 lines collapsedexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { let message = 'Oops!' let details = 'An unexpected error occurred.' let stack: string | undefined if (isRouteErrorResponse(error)) { message = error.status === 404 ? '404' : 'Error' details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details } else if (import.meta.env.DEV && error && error instanceof Error) { details = error.message stack = error.stack } return ( <main className="pt-16 p-4 container mx-auto"> <h1>{message}</h1> <p>{details}</p> {stack && ( <pre className="w-full p-4 overflow-x-auto"> <code>{stack}</code> </pre> )} </main> ) }

Run your project

Run your project with the following command:

terminal
npm run dev
terminal
pnpm run dev
terminal
yarn dev
terminal
bun run dev

Create your first user

  1. Visit your app's homepage at http://localhost:5173.
  2. Select "Sign up" on the page and authenticate to create your first user.

Next steps

Learn more about Clerk components, client-side hooks, and React Router features to add authentication to your app using the following guides.

Create a custom sign-in-or-up page

Learn how add custom sign-in-or-up page with Clerk components.

Protect content and read user data

Learn how to use Clerk's hooks and helpers to protect content and read user data in your React Router app.

Prebuilt components

Learn more about Clerk's suite of components that let you quickly add authentication to your app.

Declarative mode

Learn how to use Clerk with React Router in declarative mode to add authentication to your application.

Feedback

What did you think of this content?

Last updated on

GitHubEdit on GitHub