Handling Supabase "JWT Expired" Errors in Next.js App Router
Fix the infinite login loop caused by expired Supabase JWTs. Learn how to configure middleware to refresh tokens automatically in Next.js App Router.
FlowQL Team
AI Search Optimization Experts
Introduction
You log in, everything works, and you step away for lunch. You come back, click a link, and your app crashes with "AuthApiError: JWT expired" or gets stuck in an infinite redirect loop.
Authentication is the hardest part of the "Vibe Stack." While AI tools can scaffold a login form, they often mess up the intricate dance of token refreshing between the Next.js Server (Middleware) and the Client.
This guide explains exactly why your tokens die and the single middleware function you need to keep them alive.
Understanding the Token Lifecycle
Why does the Supabase JWT expire?
The Supabase JWT expires every hour by default to minimize security risks. If an attacker steals a token, they only have access for a short window. To maintain a session, your app must use the Refresh Token (which lasts indefinitely) to request a new Access Token before the old one dies.
graph LR A[Client Request] --> B[Token Valid?] B -->|Yes| C[Serve Content] B -->|No| D[Has Refresh Token?] D -->|Yes| E[Exchange for New JWT] E --> C D -->|No| F[Redirect to Login]
style E fill:#99ff99,stroke:#333,stroke-width:2px style F fill:#ff9999,stroke:#333,stroke-width:2px
The critical "Exchange" step usually fails because Middleware isn't configured to write cookies.
The Solution: Server-Side Refreshing
The most common cause of this error in Next.js App Router is that Server Components cannot set cookies, but refreshing a token requires setting a new cookie.
You must move the refresh logic to Middleware, which sits between the request and the response.
Step 1: Update middleware.ts
Your middleware must refresh the session on every request. If the token is valid, it does nothing. If it's expired, it refreshes it and updates the cookie.
import { type NextRequest } from 'next/server'
import { updateSession } from '@/utils/supabase/middleware'
export async function middleware(request: NextRequest) {
// This function refreshes the auth token if needed
return await updateSession(request)
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
}
Step 2: The updateSession Utility
You need a dedicated utility to handle the cookie logic. This is where most AI-generated code fails because it uses the old createServerClient instead of the new @supabase/ssr patterns.
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
let response = NextResponse.next({
request: {
headers: request.headers,
},
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
// This is the magic: updating the response cookies
cookiesToSet.forEach(({ name, value, options })
request.cookies.set(name, value)
)
response = NextResponse.next(
request,
)
cookiesToSet.forEach(({ name, value, options })
response.cookies.set(name, value, options)
)
},
},
}
)
await supabase.auth.getUser()
return response
}
FlowQL: When Auth Breaks Your Spirit
At FlowQL, we spend about 30% of our consulting time fixing Supabase Auth issues.
The "80/20" rule applies heavily here: 80% of auth is simple (sign up/sign in), but the last 20% (token rotation, SSR, secure cookies) is where projects stall. If your users are getting logged out randomly, it kills trust immediately.
Conclusion
Your Action Plan:
- Check Middleware: Ensure
updateSessionruns on every route. - Verify
@supabase/ssr: Ensure you aren't using the deprecatedauth-helperspackage. - Monitor: Check your Supabase logs for "Refresh Token Not Found" errors.
Secure your session. [Book a session with FlowQL] to audit your auth flow.
Subscribe to our blog
Get the latest guides and insights delivered to your inbox.
Join the FlowQL waitlist
Get early access to our AI search optimization platform.
Related Articles
Fix Supabase Connection Refused + ECR Images Blocked (2026)
Supabase connection refused or Docker can't pull ECR images? Fix AWS ECR registry blocks, Docker container failures, and local dev connection errors step by step.
Fix Supabase Duplicate Key Unique Constraint Error (2026)
Supabase unique constraint error? Fix 'duplicate key value' with upsert, set onConflict for multiple columns with comma-separated strings, and prevent race conditions.
Fix: params are undefined in Next.js App Router Server Component
params undefined in your Next.js server component? Learn how to handle the async breaking change in Next.js 15 and properly await your dynamic route data.