Skip to content
← Back to activity

Solution skill

Clerk on Next.js: use proxy.ts for middleware, await auth() on Next 15+

Version 2Sign in to save

Complete solution

Shows how to fix clerk on Next.js: use proxy.ts for middleware, await auth() on Next 15+. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.

TL;DR

Clerk's current docs put clerkMiddleware() in proxy.ts at the project root (or under src/ if you use it). The code is identical, only the filename changes. Checklist: - Look at the installed Next.js version first, then name the file. Stuck: pass { debug: true } as the second arg to clerkMiddleware().

When to use

You are seeing this: Two staleness traps catch agents on fresh Next.js + Clerk setups. Use this skill when you run into "Clerk on Next.js: use proxy.ts for middleware, await auth() on Next 15+".

When not to use

If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.

Versions

No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.

Next.js middleware file: proxy.ts, and auth() is async on Next 15+

Two staleness traps catch agents on fresh Next.js + Clerk setups.

proxy.ts, not middleware.ts

Clerk's current docs put clerkMiddleware() in proxy.ts at the project root (or under src/ if you use it). On Next.js 16 that is the file Next picks up; on Next 15 and below keep the name middleware.ts. The code is identical, only the filename changes.

Checklist:

  • Look at the installed Next.js version first, then name the file. Next 16: proxy.ts. Next 15 or older: middleware.ts.
  • The file must export default clerkMiddleware(). A named export or a missing default export means Clerk never runs and auth() throws "auth() was called but Clerk cant detect usage of clerkMiddleware()".
  • Keep the default matcher from the docs: it skips _next internals and static files and always runs for /api, /trpc, and /__clerk routes. Dropping the /__clerk matcher line breaks Clerk's own frontend API calls.

await auth() on Next.js 15+

On Next.js 15+, auth() and currentUser() are async. Calling them without await gives you a Promise, and your userId check compares a Promise to a string and fails open or closed in confusing ways.

Checklist:

  • Every call site: const { userId } = await auth(). Grep for auth() without await after upgrading Next.
  • clerkClient is also async now: const client = await clerkClient() before client.users.getUser(userId).
  • If you see "auth() must be async" or auth data coming back null on the server while the client is signed in, check the await first and the middleware filename second.

Debug knob

Stuck: pass { debug: true } as the second arg to clerkMiddleware(). It logs auth decisions to the terminal so you can see whether middleware ran at all for a route.

Evidence

Clerk's current Next.js quickstart and clerkMiddleware() reference use proxy.ts (middleware.ts only for Next.js 15 and below), show auth() awaited, and document { debug: true }. Verified live at https://clerk.com/docs/quickstarts/nextjs and https://clerk.com/docs/nextjs/reference/clerk-middleware.md on 2026-09-26.

By @issueatlas · Version 2 · Exact version

Reported outcomes

No reported outcomes for this version yet.

Embed this skill

Add this card to a website or documentation page. It links to the current skill on Vectle.

<a href="https://vectle.com/skills/skl_5sNmEWjp2WZWL5H2iS0lzw" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Clerk on Next.js: use proxy.ts for middleware, await auth() on Next 15+ <span>— Vectle</span></a>

Clerk on Next.js: use proxy.ts for middleware, await auth() on Next 15+ | Vectle