Skip to content
Loadout

Fix

Next.js 16 middleware.ts vs proxy.ts

Answer

In Next.js 16 the request interceptor is proxy.ts exporting proxy. middleware.ts is deprecated and still runs, including on the Edge runtime. proxy.ts runs on Node.js only.

  • Next.js 16.4.1
  • React 19.3
  • @supabase/ssr 0.12.7
  • @supabase/supabase-js current, installed alongside @supabase/ssr

Last verified 2026-10-15

Agents still treat a Next.js 16 middleware.ts file as dead code. The filename is deprecated in favor of proxy, and middleware.ts still runs. The Next.js + Supabase pack pins Next.js 16.4.1, React 19.3, @supabase/ssr 0.12.7, and @supabase/supabase-js (current, installed alongside @supabase/ssr). Do not generate Pages Router or @supabase/auth-helpers-nextjs.

Migrate when you do not need Edge. Rename middleware.ts to proxy.ts and rename the exported function to proxy. The proxy runtime is Node.js and cannot be configured. Keep middleware.ts when the interceptor must stay on Edge. Next.js 16 still runs that file. It is deprecated and will be removed in a future version.

For a Supabase session in this pack, use proxy.ts and export proxy. proxy.ts refreshes the session by calling getClaims(), copies cookies onto both the request and the response, and returns the response that setAll last built. getClaims() verifies the JWT, locally against JWKS on asymmetric keys, which new projects use. Pages and actions still call getClaims() themselves. Proxy is not the only lock. A middleware.ts file that already refreshes the session keeps doing that until you migrate it.

In a Server Component, setAll cannot write cookies. Catch and ignore that error. Refresh the session in proxy.ts, or in the middleware.ts file you kept for Edge. setAll receives a second argument, the cache headers. Apply Cache-Control, Expires, and Pragma on the response. Skipping them lets a CDN store one user's session cookie and serve it to someone else. New create-next-app projects enable Cache Components. Do not cache a response that depends on a cookie or a user. Authenticated reads stay dynamic. Browser code uses createBrowserClient. Server Components, Server Actions, and Route Handlers use createServerClient with cookies.getAll and cookies.setAll.

Install @supabase/supabase-js and @supabase/ssr. Never import @supabase/auth-helpers-nextjs. Env names from the current docs are NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY. The secret key and the service-role key stay server-only, with no NEXT_PUBLIC_ prefix. Use getSession() only to read the raw token, and never trust session.user for authorization. Use getUser() only when you need the current user row from the Auth server. Sign-in and sign-out go through Server Actions, not a client fetch to your own route.

Last verified 2026-10-15. doc-verified; TypeScript snippets pass tsc --strict (TypeScript 5, @types/react 19) with zod and the Supabase client stubbed.

Next.js + Supabase pack — Next.js 16.4 and Supabase SSR, with the APIs that actually work.

doc-verified; TypeScript snippets pass tsc --strict (TypeScript 5, @types/react 19) with zod and the Supabase client stubbed

Sources in the pack

Questions

Does middleware.ts run in Next.js 16?
In Next.js 16 the request interceptor is proxy.ts exporting proxy. middleware.ts is deprecated and still runs, including on the Edge runtime. proxy.ts runs on Node.js only.
What does proxy.ts do for a Supabase session?
proxy.ts refreshes the session by calling getClaims(), copies cookies onto both the request and the response, and returns the response that setAll last built. Pages and actions still call getClaims() themselves. Proxy is not the only lock.
When should I keep middleware.ts in Next.js 16?
Keep middleware.ts when you need the Edge runtime. proxy.ts runs on Node.js, and that runtime cannot be set to Edge. middleware.ts is deprecated and still runs. Rename the file to proxy.ts, and rename the export to proxy, when Edge is not required.
When was this Next.js 16 proxy rule last checked?
Last verified 2026-10-15. doc-verified; TypeScript snippets pass tsc --strict (TypeScript 5, @types/react 19) with zod and the Supabase client stubbed.