Short answer
In a Next.js or other server-rendered app, use the @supabase/ssr clients so the session lives in cookies, and add the middleware (proxy.ts on Next.js 16) that refreshes it on every request. In a browser-only app, create one Supabase client with persistSession on, and wait for the stored session to load before your route guard sends anyone to the login page.
Why the session disappears, most common cause first
01
Next.js has no middleware refreshing the session
“Since Next.js Server Components can't write cookies, you need a Proxy to refresh expired Auth tokens and store them.” Supabase, Creating a Supabase client for SSR
Server Components can read the auth cookie but can’t write a refreshed one, so a Next.js app needs the middleware from Supabase’s guide. Name the file for your version: proxy.ts with a proxy function on Next.js 16, middleware.ts with a middleware function before that. A proxy.ts on Next.js 15 is ignored, and users are signed out once the access token expires. Also check the matcher doesn’t skip the pages users land on.
02
The session is in local storage, but the server checks cookies
“You need to make a few changes to the configuration of your Supabase client, to store the user session in cookies instead of local storage.” Supabase, Server-Side Rendering
The plain supabase-js client saves the session in the browser’s local storage, which the server never sees. A server-rendered page then finds no session and redirects to login on every refresh, even though the browser is signed in. Switch to createBrowserClient and createServerClient from @supabase/ssr, which store the session in cookies.
import { createBrowserClient } from '@supabase/ssr'; export const supabase = createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!, );03
The route guard runs before the session loads
On a fresh page load the client reads the stored session asynchronously. A guard that checks for a user on the first render sees none and redirects to login, which looks like a lost session. Show a loading state until onAuthStateChange fires its INITIAL_SESSION event, then decide. Create the client once in a shared module: a second client with persistSession set to false, or configured with a different storage key, won’t see the saved session.
04
Nothing refreshes the access token
“Access tokens are designed to be short lived, usually between 5 minutes and 1 hour while refresh tokens never expire but can only be used once.” Supabase, User sessions
Access tokens are short lived and the refresh token renews them. If autoRefreshToken is off, or the app calls the API with a token it saved itself, requests start failing when that token expires. In React Native, pass AsyncStorage as the client’s storage and start and stop auto refresh with the app’s foreground state, as Supabase’s quickstart shows.
05
A session setting ends sessions early
“Sessions are not proactively destroyed when you change these settings, but rather the check is enforced whenever a session is refreshed next.” Supabase, User sessions
On Pro plans and up, the Auth settings Time-box user sessions, Inactivity timeout and Single session per user all end sessions on purpose. If one is on, users are signed out at the next refresh after the limit, which can feel random. Turn it off or raise it if that isn’t what you want.
Checked against the vendor’s docs on : Supabase docs: Creating a Supabase client for SSR (Next.js); Supabase docs: Server-Side Rendering; Supabase docs: User sessions; Supabase reference: getSession; Supabase reference: onAuthStateChange; Supabase docs: React Native quickstart.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way.See scope and start
Related: Supabase auth emails link to localhost · Google sign-in redirect_uri_mismatch · Supabase RLS fixes · Supabase public storage bucket