Skip to main content

Next.js rendering modes and Clerk

By default, Next.js attempts to optimize your application by statically generating pages at build-time that do not depend on data from the request. However, authentication data is inherently dynamic and depends on the request, meaning that it is not available at build-time.

Tip

Read more about static and dynamic rendering in the Next.js documentation.

To facilitate Next.js's default behavior, Clerk provides an opt-in approach to accessing authentication data so that you can opt-in specific routes to dynamic rendering, while still using statically rendered pages for others.

The following options are available for accessing authentication data:

  • The auth() helper can only be used in Server Components, but the data can be passed to Client Components if desired. Without Cache Components, it opts your entire route into dynamic rendering.
  • The useAuth() hook can only be used in Client Components. Due to Next.js's default behavior, these components will be statically rendered. However, you can wrap them with <ClerkProvider dynamic> to opt them into dynamic rendering.

Note

New Next.js apps enable Cache Components, which prerenders each page up to its <Suspense> boundaries instead of rendering the whole route dynamically. If your app sets cacheComponents: true, see Cache Components for where to add <Suspense> boundaries.

Access auth data with auth()

The auth()Next.js Icon helper returns the user's authentication state in Server Components. The data can be passed to Client Components if desired. This is a dynamic API that relies on request-time data. Without Cache Components, using auth() opts your entire route into dynamic rendering, as in the following example. With Cache Components, only the content inside the <Suspense> boundary around auth() renders at request time, so this page needs a boundary, such as a loading.tsx file.

import { auth } from '@clerk/nextjs/server'

// This page will be dynamically rendered at request time
export default async function Page() {
  const { userId } = await auth()

  // This will be immediately available on first render
  console.log(userId)

  return <p>Hello, {userId}</p>
}

Access auth data with useAuth()

useAuth() provides authentication data in Client Components. Due to Next.js's default behavior, these components will be statically rendered. If that's fine with you, see the reference docs for more information, like code examples.

However, if you'd like the components that use useAuth() to be dynamically rendered, you can wrap them with <ClerkProvider dynamic>. Wrap <ClerkProvider dynamic> in <Suspense> so that the boundary fallback is included in the prerendered HTML. If you're using Cache Components, the boundary is required, or next build fails.

Warning

It is not recommended and not optimal to wrap your entire application with <ClerkProvider dynamic> as this opts all routes into dynamic rendering, when some routes may be better suited for static rendering. If you're not sure if a route is better suited for static or dynamic rendering, see the Next.js guide on static and dynamic rendering.

The following example demonstrates how to use <ClerkProvider dynamic> in a page's layout in order to opt it into dynamic rendering. In this case, the root layout would still be wrapped with <ClerkProvider> to provide the authentication context to the entire app, but without the dynamic prop being passed as to not opt your entire app into dynamic rendering.

app/example/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
import { Suspense } from 'react'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <Suspense fallback={<Skeleton />}>
      <ClerkProvider dynamic>{children}</ClerkProvider>
    </Suspense>
  )
}

function Skeleton() {
  return <div>Loading...</div>
}
app/example/page.tsx
'use client'

import { useAuth } from '@clerk/nextjs'

export default function Page() {
  const { userId, sessionId, isSignedIn } = useAuth()

  // All of these will be immediately available on first render
  console.log(userId, sessionId, isSignedIn)

  return (
    <div>
      <h1>Test Page</h1>
      <p>User ID: {userId}</p>
    </div>
  )
}

Cache Components

New Next.js apps created with create-next-app enable Cache Components by setting cacheComponents: true in next.config.ts. With Cache Components, Next.js prerenders as much of each page as it can at build time and streams in the parts that depend on the request. Any code that reads request data must run inside a <Suspense> boundary. Otherwise, next build fails with an error like the following:

Error: Route "/": Next.js encountered uncached or runtime data during prerendering.

Clerk reads the user's session from the request, so the following need a <Suspense> boundary:

Without the dynamic prop, <ClerkProvider> doesn't need a boundary. Place it inside <body>, as shown in the root layout example.

Read auth data inside <Suspense>

Place each <Suspense> boundary as close as possible to the component that reads auth data. Next.js prerenders everything outside the boundary as static HTML, shows the boundary's fallback in its place, and streams in the auth-dependent content at request time.

The following example demonstrates how to wrap the <Show> components in a root layout's header. <ClerkProvider> and <header> stay outside the boundary, so they're part of the prerendered page.

app/layout.tsx
import { ClerkProvider, Show, SignInButton, SignUpButton, UserButton } from '@clerk/nextjs'
import { Suspense } from 'react'

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body>
        <ClerkProvider>
          <header>
            <Suspense>
              <Show when="signed-out">
                <SignInButton />
                <SignUpButton />
              </Show>
              <Show when="signed-in">
                <UserButton />
              </Show>
            </Suspense>
          </header>
          {children}
        </ClerkProvider>
      </body>
    </html>
  )
}

If a Server Component page renders <Show>, wrap the <Show> in <Suspense> the same way.

If a page calls auth() or currentUser() to render data, move the call into an async child component inside <Suspense>, and keep the page itself synchronous. The following example demonstrates how to call currentUser() in a page. The page's heading is prerendered, while the <Greeting> component reads the user inside a <Suspense> boundary and streams in at request time.

app/dashboard/page.tsx
import { currentUser } from '@clerk/nextjs/server'
import { Suspense } from 'react'

export default function Page() {
  return (
    <main>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>Loading...</p>}>
        <Greeting />
      </Suspense>
    </main>
  )
}

async function Greeting() {
  const user = await currentUser()

  if (!user) {
    return <p>Sign in to see your dashboard.</p>
  }

  return <p>Welcome, {user.firstName}!</p>
}

If a page gates its whole render with auth.protect(), redirectToSignIn(), or redirect(), leave the page unchanged and add a loading.tsx file next to it. Next.js wraps the page in a <Suspense> boundary that uses loading.tsx as its fallback, so the page can call auth() at the top level. The trade-off is that Next.js prerenders only the layout and the fallback, not any of the page's own content. Next.js also sends the response status as soon as it starts streaming, so a signed-out or unauthorized request to that page gets HTTP 200 with the redirect or 404 streamed in, not a 307 or 404. To send signed-out users a real 307, add an early redirect in clerkMiddleware(), which runs before rendering, and keep the page's own check.

app/settings/page.tsx
import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  const { userId } = await auth.protect()

  return <p>Settings for {userId}</p>
}
app/settings/loading.tsx
export default function Loading() {
  return <p>Loading...</p>
}

Prebuilt components on catch-all routes

<SignIn />, <SignUp />, <UserProfile />, and <OrganizationProfile /> read the current URL with Next.js's usePathname() and useParams() hooks to support path-based routing. On an optional catch-all route, such as app/sign-in/[[...sign-in]]/page.tsx, Next.js only knows the URL at request time. Without a <Suspense> boundary, next build fails with an error like the following:

Error: Route "/sign-in/[[...sign-in]]": Next.js encountered URL data `usePathname()` in a Client Component outside of `<Suspense>`.

The following example demonstrates how to wrap <SignIn /> in <Suspense>. The same applies to the other components, even when you pass the path prop or render the component from a Client Component.

app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs'
import { Suspense } from 'react'

export default function Page() {
  return (
    <Suspense>
      <SignIn />
    </Suspense>
  )
}

Cache data per user with 'use cache'

auth() and currentUser() read the request's headers, which aren't available inside a 'use cache' function. Calling them there fails with the following error:

Error: Route "/": `headers()` can't be read inside `"use cache"`. Read it outside the cached function and pass what you need as an argument.
Learn more: https://nextjs.org/docs/messages/next-request-in-use-cache

Instead, read the user's ID outside the cached function and pass it in as an argument. Next.js includes a cached function's arguments in its cache key, so each user gets their own cache entry.

The following example demonstrates how to cache a user's projects. The <Projects> component reads the user's ID with auth() inside a <Suspense> boundary and passes it to getProjects(), which caches the result for that user. cacheTag() tags the entry with the user's ID so you can invalidate that user's cache when their data changes, and cacheLife() sets how long the entry lasts.

app/projects/page.tsx
import { auth } from '@clerk/nextjs/server'
import { cacheLife, cacheTag } from 'next/cache'
import { Suspense } from 'react'

export default function Page() {
  return (
    <main>
      <h1>Projects</h1>
      <Suspense fallback={<p>Loading...</p>}>
        <Projects />
      </Suspense>
    </main>
  )
}

async function Projects() {
  const { userId } = await auth()
  if (!userId) {
    return <p>Sign in to see your projects.</p>
  }

  const projects = await getProjects(userId)

  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  )
}

async function getProjects(userId: string) {
  'use cache'
  cacheTag(`projects-${userId}`)
  cacheLife('hours')

  const res = await fetch(`https://api.example.com/users/${userId}/projects`)
  return (await res.json()) as { id: string; name: string }[]
}

Warning

Pass the user's ID, or the Organization ID for Organization-scoped data, as an argument to every cached function that returns data for that user or Organization. If the cache key doesn't include it, Next.js can serve one user's cached data to another.

Feedback

What did you think of this content?

Last updated on