Back to blog
·6 min read

How to Fix Supabase Auth Logout Issues in Next.js 15: The Session Persistence Problem Solved

Your Supabase-backed Next.js app logs users out after a few hours, even though authentication works locally. This guide reveals the session persistence problem and the exact fix developers need to keep users authenticated in production.

The Problem: Users Getting Logged Out Without Warning

You build a Next.js 15 app with Supabase auth. Everything works perfectly in development. You deploy to production and within hours, support tickets roll in: "Why does it log me out?" or worse, "I'm logged out but the login page keeps redirecting me back."

The UI shows guest status. The user refreshes. Suddenly they're authenticated again. This happens randomly every few hours.

This is one of the most common production issues developers face when combining Supabase with Next.js App Router, and the fix isn't documented clearly anywhere. It's not actually about Supabase breaking—it's about how Next.js handles session state across server and client boundaries.

Why This Happens: The Auth State Mismatch

When you initialize Supabase client-side, it stores the session in localStorage (browser) and potentially in cookies. But here's the problem: Next.js server components can't see localStorage, and if your cookie isn't configured correctly, the server has no idea who the user is.

This creates a state mismatch:

  • Server thinks: "This user isn't authenticated"
  • Client thinks: "This user is authenticated (localStorage says so)"
  • When the user navigates, the server renders a guest version of the page. The client rehydrates and sees the session in localStorage. The page flickers, redirects loop, or the user appears logged out until they refresh.

    The core issue: you're relying on localStorage as your source of truth, but Next.js server components can't access it.

    The Real Fix: Configure Supabase Sessions in Cookies

    Supabase can store sessions in cookies instead of relying on localStorage alone. This solves the server-client mismatch because:

  • Cookies are sent with every HTTP request
  • The server can read cookies before rendering
  • Server and client stay in sync
  • Here's the correct setup for Next.js 15 with App Router:

    First, install the required package:

    npm install @supabase/ssr

    Create a utility file to initialize the Supabase client correctly:

    // lib/supabase.ts

    import { createBrowserClient } from '@supabase/ssr'

    export function createClient() {

    return createBrowserClient(

    process.env.NEXT_PUBLIC_SUPABASE_URL,

    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,

    {

    cookies: {

    getAll() {

    // In browser context, use document.cookie

    if (typeof document === 'undefined') return []

    return document.cookie

    .split('; ')

    .map(c => {

    const [name, ...rest] = c.split('=')

    return { name, value: decodeURIComponent(rest.join('=')) }

    })

    },

    setAll(cookies) {

    // This tells Supabase to use cookies

    if (typeof document === 'undefined') return

    cookies.forEach(({ name, value, options }) => {

    document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${options.maxAge}`

    })

    },

    },

    }

    )

    }

    The critical part: those cookie handlers tell Supabase to sync sessions via cookies, not just localStorage.

    Sync Auth State Across Layouts

    Next, create a provider component that runs early in your layout to refresh the auth session:

    // app/providers.tsx

    'use client'

    import { useEffect } from 'react'

    import { createClient } from '@/lib/supabase'

    export function AuthProvider({ children }) {

    const supabase = createClient()

    useEffect(() => {

    // Refresh session on mount

    supabase.auth.getSession()

    // Listen for auth changes and sync across tabs

    const {

    data: { subscription },

    } = supabase.auth.onAuthStateChange((event, session) => {

    // This runs when auth state changes

    // The session is now synced via cookies

    })

    return () => {

    subscription?.unsubscribe()

    }

    }, [supabase])

    return <>{children}</>

    }

    Add this to your root layout:

    // app/layout.tsx

    import { AuthProvider } from './providers'

    export default function RootLayout({ children }) {

    return (

    <html>

    <body>

    <AuthProvider>

    {children}

    </AuthProvider>

    </body>

    </html>

    )

    }

    Use Server Components Correctly

    For protected routes, fetch the user session server-side using the Supabase server client, not the browser client:

    // lib/supabase-server.ts

    import { createServerClient } from '@supabase/ssr'

    import { cookies } from 'next/headers'

    export function createServerClient() {

    const cookieStore = cookies()

    return createServerClient(

    process.env.NEXT_PUBLIC_SUPABASE_URL,

    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,

    {

    cookies: {

    getAll() {

    return cookieStore.getAll()

    },

    setAll(cookies) {

    cookies.forEach(({ name, value, options }) => {

    cookieStore.set(name, value, options)

    })

    },

    },

    }

    )

    }

    Now protect pages server-side:

    // app/dashboard/page.tsx

    import { createServerClient } from '@/lib/supabase-server'

    import { redirect } from 'next/navigation'

    export default async function DashboardPage() {

    const supabase = createServerClient()

    const { data } = await supabase.auth.getUser()

    if (!data.user) {

    redirect('/login')

    }

    return <div>Welcome {data.user.email}</div>

    }

    The server checks authentication before rendering. No flicker. No mismatch.

    Three More Production Safeguards

    Beyond session persistence, add these protections:

    Enable CORS on your Supabase project for your exact domain, not wildcards. Set your session timeout to something reasonable (default is 1 hour). Most logout complaints are actually just expired sessions, not bugs.

    Also, monitor your Supabase auth logs. Go to the Authentication section of your dashboard and check for failed session refreshes. If you see patterns, it usually points to cookie configuration issues.

    Why AI-Assisted Development Makes This Worse

    Here's what often happens: Claude Code generates a basic Supabase setup quickly. It works immediately. You ship it. Production breaks.

    Why? Because AI assistants optimize for "working locally," not "production-ready." The default setup uses localStorage only. It's fewer lines of code. It feels complete because the demo works.

    Real production software requires this session persistence layer, but AI won't generate it unless you specifically ask about server-side authentication or session management. This is why the feedback loop matters: generate, test in production-like conditions, then improve.

    If you're building with AI assistance, always ask: "How does this handle session persistence across page reloads in production?" That single question would have prevented this entire class of bugs.

    Tools like ZipBuild scaffold this session layer from the start—the cookie configuration, server client setup, and auth state sync are all pre-configured so you don't ship with the wrong defaults.

    Quick Checklist

  • Supabase client uses cookie storage, not localStorage only
  • AuthProvider runs on app startup to sync sessions
  • Server components use the server client, not the browser client
  • Protected pages redirect unauthenticated users server-side, not client-side
  • Session timeout is configured and monitored
  • Test logout behavior with fresh browser sessions, not just dev mode
  • The logout problem disappears once sessions persist correctly. Your users stay logged in, and more importantly, they stay logged in across browser refreshes and tab switches.

    Try the free discovery chat at zipbuild.dev to see how production-ready scaffolds handle auth from day one.

    Written by ZipBuild Team

    Ready to build with structure?

    Try the free discovery chat and see how ZipBuild architects your idea.

    Start Building