Auth & Data
Use when a Next.js app uses domain-locked production Clerk keys, so local development cannot run clerkMiddleware and auth() throws in route handlers. Also use when a dashboard route 500s locally but works deployed, or an API returns 404 where it should return 401. Covers the middleware bypass and a null-safe server helper. If auth works in production but not locally, use this skill.
1 file
Description
Use when a Next.js app uses domain-locked production Clerk keys, so local development cannot run clerkMiddleware and auth() throws in route handlers. Also use when a dashboard route 500s locally but works deployed, or an API returns 404 where it should return 401. Covers the middleware bypass and a null-safe server helper. If auth works in production but not locally, use this skill.
Local dev 500s on a route that reads auth(), the app only has production Clerk keys (domain-locked to the live host), or you need API routes to behave for signed-out callers instead of crashing.
Production Clerk keys are locked to the production domain. On localhost they cannot initialize, so if middleware.ts runs clerkMiddleware() in dev, requests fail. The common workaround is to skip clerkMiddleware() in development. But then auth() in a route handler runs without the middleware context and THROWS rather than returning a signed-out state.
Never call auth() directly in a route handler. Wrap it so a throw becomes "signed out":
import { auth } from "@clerk/nextjs/server";
export async function getUserId(): Promise<string | null> {
try {
const { userId } = await auth();
return userId ?? null;
} catch {
return null; // dev middleware bypass makes auth() throw; treat as signed out
}
}
Every API route calls getUserId() and returns its own 401 when it gets null. This works in both dev (throw caught) and prod (real null).
auth.protect() in middleware redirects browser requests but returns a bare 404 for API paths, which is confusing to integrators. For APIs, do not rely on protect(): read the user id in the handler and return an explicit 401 with a clear body.
auth() directly in a route handler works in production and 500s in dev under the bypass. Always go through the guarded helper..env.local and the host env separately.useAuth() / useUser() belong in client components; auth() and the helper belong on the server. Mixing them throws.Related
Added 2026-07-01. Back to the Skill Library.

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.