Created
July 20, 2026 09:43
-
-
Save CodeLikeAGirl29/46fb0a1c84225fb6b4c02f3734f4ac9e to your computer and use it in GitHub Desktop.
Next.js App Router middleware for Supabase SSR auth. Refreshes the session on every request and redirects unauthenticated users away from protected routes (and logged-in users away from login/signup). Handles the cookie-forwarding boilerplate that's easy to get wrong — skip the getUser() call and you get random logouts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Next.js App Router + Supabase SSR auth middleware | |
| * | |
| * Handles session refresh on every request and redirects unauthenticated | |
| * users away from protected routes. Drop this in `middleware.ts` at your | |
| * project root. | |
| * | |
| * Requires: @supabase/ssr, @supabase/supabase-js | |
| * npm install @supabase/ssr @supabase/supabase-js | |
| * | |
| * Env vars needed: | |
| * NEXT_PUBLIC_SUPABASE_URL | |
| * NEXT_PUBLIC_SUPABASE_ANON_KEY | |
| */ | |
| import { createServerClient } from '@supabase/ssr' | |
| import { NextResponse, type NextRequest } from 'next/server' | |
| // Routes that require a logged-in user. Adjust to match your app. | |
| const PROTECTED_PATHS = ['/dashboard', '/admin', '/account'] | |
| // Routes a logged-in user shouldn't see (e.g. login/signup pages) | |
| const AUTH_ONLY_PATHS = ['/login', '/signup'] | |
| export async function updateSession(request: NextRequest) { | |
| let supabaseResponse = NextResponse.next({ request }) | |
| const supabase = createServerClient( | |
| process.env.NEXT_PUBLIC_SUPABASE_URL!, | |
| process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, | |
| { | |
| cookies: { | |
| getAll() { | |
| return request.cookies.getAll() | |
| }, | |
| setAll(cookiesToSet) { | |
| cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value)) | |
| supabaseResponse = NextResponse.next({ request }) | |
| cookiesToSet.forEach(({ name, value, options }) => | |
| supabaseResponse.cookies.set(name, value, options) | |
| ) | |
| }, | |
| }, | |
| } | |
| ) | |
| // IMPORTANT: this call refreshes the session token if it's expired. | |
| // Do not remove — skipping it causes random logouts. | |
| const { | |
| data: { user }, | |
| } = await supabase.auth.getUser() | |
| const path = request.nextUrl.pathname | |
| const isProtected = PROTECTED_PATHS.some((p) => path.startsWith(p)) | |
| const isAuthOnly = AUTH_ONLY_PATHS.some((p) => path.startsWith(p)) | |
| if (isProtected && !user) { | |
| const redirectUrl = request.nextUrl.clone() | |
| redirectUrl.pathname = '/login' | |
| redirectUrl.searchParams.set('redirectedFrom', path) | |
| return NextResponse.redirect(redirectUrl) | |
| } | |
| if (isAuthOnly && user) { | |
| const redirectUrl = request.nextUrl.clone() | |
| redirectUrl.pathname = '/dashboard' | |
| return NextResponse.redirect(redirectUrl) | |
| } | |
| return supabaseResponse | |
| } | |
| // middleware.ts | |
| // ----------------------------------------------------------------------- | |
| // import { updateSession } from './lib/supabase/middleware' | |
| // | |
| // export async function middleware(request: NextRequest) { | |
| // return await updateSession(request) | |
| // } | |
| // | |
| // export const config = { | |
| // matcher: [ | |
| // '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', | |
| // ], | |
| // } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment