Skip to content

Instantly share code, notes, and snippets.

@CodeLikeAGirl29
Created July 20, 2026 09:43
Show Gist options
  • Select an option

  • Save CodeLikeAGirl29/46fb0a1c84225fb6b4c02f3734f4ac9e to your computer and use it in GitHub Desktop.

Select an option

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.
/**
* 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