Internal DocumentationArchived DocumentationDo dev legacyArchiveClerk migration

Clerk + Next.js 15 Headers Error Solution

The Issue (Fixed in v6+)

Clerk's middleware in versions prior to v6 caused a headers error in Next.js 15:

Error: Route "/" used `...headers()` or similar iteration. 
headers() should be awaited before using its value.

This happened because older versions of Clerk's middleware internally accessed headers synchronously, which violated Next.js 15's requirement that all request APIs must be awaited.

The Solution

Update to Clerk v6.28.0 or later, which fixes the headers error with Next.js 15.

1. Update Clerk Package

pnpm add @clerk/nextjs@latest
# or
npm install @clerk/nextjs@latest

2. Use Standard Clerk Middleware (middleware.ts)

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

export default clerkMiddleware()

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    '/(api|trpc)(.*)',
  ],
}

3. Protecting Pages

Use Clerk's hooks in client components:

"use client"
import { useAuth } from "@clerk/nextjs"
import { redirect } from "next/navigation"

export default function ProtectedPage() {
  const { isLoaded, isSignedIn } = useAuth()
  
  if (!isLoaded) return <div>Loading...</div>
  
  if (!isSignedIn) {
    redirect("/sign-in")
  }
  
  return <div>Protected content</div>
}

4. Protecting API Routes

Use currentUser() helper in API routes:

import { currentUser } from "@clerk/nextjs/server"
import { NextRequest, NextResponse } from "next/server"

export async function GET(request: NextRequest) {
  const user = await currentUser()
  
  if (!user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
  }
  
  // Protected API logic here
  // You can access user.id, user.emailAddresses, etc.
}

Note: With Clerk v6+, both auth() and currentUser() work correctly in API routes.

5. Server Components

For server components, use Clerk's server-side helpers:

import { currentUser } from "@clerk/nextjs/server"
import { redirect } from "next/navigation"

export default async function ServerProtectedPage() {
  const user = await currentUser()
  
  if (!user) {
    redirect("/sign-in")
  }
  
  return <div>Hello {user.firstName}</div>
}

Important Notes

  • Clerk v6.28.0+ fixes the headers error with Next.js 15
  • Use standard clerkMiddleware() configuration
  • Use currentUser() or auth() in API routes
  • All authentication features work normally

Migration from v5 to v6

If you're upgrading from Clerk v5:

  1. Update to v6: pnpm add @clerk/nextjs@latest
  2. Replace any custom middleware workarounds with standard clerkMiddleware()
  3. Replace getAuth(request) with auth() or currentUser() in API routes

Status

  • Tested with @clerk/nextjs v6.28.0 and Next.js 15.4.4
  • The headers error is completely eliminated
  • All authentication features work correctly

On this page