Integrating Clerk with Next.js App Router
Enforces current and correct instructions for integrating Clerk into Next.js App Router applications.
Quick Start
For a typical integration:
- •Install
@clerk/nextjs@latest - •Set up environment variables in
.env.local - •Create
middleware.tswithclerkMiddleware() - •Wrap app with
<ClerkProvider>inapp/layout.tsx - •Add Clerk components (
<SignInButton>,<UserButton>, etc.)
Complete Integration Workflow
Step 1: Install Clerk SDK
npm install @clerk/nextjs
Step 2: Configure Environment Variables
Action: Create or update .env.local with Clerk API keys.
CRITICAL:
- •NEVER write actual keys to tracked files
- •ONLY use placeholder values in code examples
- •Verify
.gitignoreexcludes.env*files
# .env.local NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY CLERK_SECRET_KEY=YOUR_SECRET_KEY
Get keys from: Clerk Dashboard → API Keys
Step 3: Create Middleware
File: middleware.ts (at project root, or in src/ if using src directory)
// middleware.ts
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
// Always run for API routes
"/(api|trpc)(.*)",
],
};
CRITICAL: Use clerkMiddleware() from @clerk/nextjs/server - NOT authMiddleware() (deprecated).
Step 4: Wrap App with ClerkProvider
File: app/layout.tsx
Before editing:
- •Read the existing
app/layout.tsxfile - •Identify the current structure
- •Preserve existing imports and metadata
Add these imports:
import {
ClerkProvider,
SignInButton,
SignUpButton,
SignedIn,
SignedOut,
UserButton,
} from "@clerk/nextjs";
Wrap the app:
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider>
<html lang="en">
<body>
<header>
<SignedOut>
<SignInButton />
<SignUpButton />
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</header>
{children}
</body>
</html>
</ClerkProvider>
);
}
Step 5: Verify Implementation
Run through this checklist before completing:
- • Middleware:
clerkMiddleware()used inmiddleware.ts - • Layout:
<ClerkProvider>wraps app inapp/layout.tsx - • Imports: References from
@clerk/nextjsor@clerk/nextjs/server - • App Router: Using
app/directory (notpages/or_app.tsx) - • Environment Variables: Only placeholders in code, actual keys in
.env.local - • File Security:
.env*in.gitignore
Step 6: Test the Integration
npm run dev
Navigate to the app and verify:
- •Sign-in/sign-up buttons appear for unauthenticated users
- •User button appears for authenticated users
- •Authentication flow works end-to-end
Server-Side Authentication
When accessing authentication data in Server Components or API routes:
import { auth } from "@clerk/nextjs/server";
export default async function Page() {
const { userId } = await auth();
if (!userId) {
return <div>Not authenticated</div>;
}
return <div>User ID: {userId}</div>;
}
CRITICAL:
- •Import
authfrom@clerk/nextjs/server - •Always use
await auth()(it's async)
Protected Routes
To protect specific routes, update middleware.ts:
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/profile(.*)',
]);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
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)(.*)",
],
};
CRITICAL: What NOT to Do
NEVER do the following:
- •❌ Use
authMiddleware()- it's deprecated, useclerkMiddleware() - •❌ Reference
_app.tsxorpages/directory for Clerk setup - •❌ Import from deprecated APIs (
withAuth, oldcurrentUser) - •❌ Write actual API keys to tracked files
- •❌ Use sync
auth()- it's async, always useawait auth() - •❌ Forget to wrap with
<ClerkProvider>in layout
See reference/outdated-patterns.md for detailed examples of deprecated approaches.
Troubleshooting
Issue: "clerkMiddleware is not a function"
- •Cause: Using old version of
@clerk/nextjs - •Fix: Run
npm install @clerk/nextjs@latest
Issue: Authentication not working
- •Check: Is
middleware.tsat the root (or insrc/if using src directory)? - •Check: Are environment variables set correctly in
.env.local? - •Check: Is the app wrapped with
<ClerkProvider>?
Issue: TypeScript errors with auth()
- •Cause: Not awaiting async
auth()function - •Fix: Change
const { userId } = auth()toconst { userId } = await auth()
Best Practices
- •Environment Variables: Always use
.env.localfor local development - •Type Safety: Use TypeScript for better autocomplete and type checking
- •Protected Routes: Use
createRouteMatcherfor route-based protection - •Server Components: Leverage
auth()for server-side authentication checks - •Error Handling: Wrap authentication calls in try-catch for production apps
Additional Resources
Example Implementation
Here's a complete minimal example:
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)(.*)",
],
};
app/layout.tsx:
import { ClerkProvider, SignInButton, SignedIn, SignedOut, UserButton } from "@clerk/nextjs";
import "./globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider>
<html lang="en">
<body>
<header>
<SignedOut>
<SignInButton />
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</header>
<main>{children}</main>
</body>
</html>
</ClerkProvider>
);
}
app/page.tsx:
import { auth } from "@clerk/nextjs/server";
export default async function Home() {
const { userId } = await auth();
return (
<div>
<h1>Welcome {userId ? "back" : "to our app"}!</h1>
</div>
);
}
.env.local:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... CLERK_SECRET_KEY=sk_test_...