Stephen

Hackett-Delaney

Full Stack Software Engineer

Articles

Supabase Auth and the SSR Gotcha

supabasetanstack-startauthenticationssr

February 19, 2026

Hard refresh. Redirected to /login. But you're logged in.

What's Happening

TanStack Start is a full-stack framework. On initial page load — or any hard refresh — the server handles the request before the browser does anything. Route guards run in beforeLoad, which executes server-side.

The Supabase JS client stores your session token in localStorage. That's a browser API. The server has no access to it. So when beforeLoad calls supabase.auth.getSession(), it gets back null. No session. Redirect to login.

Your session is fine. The server just can't see it.

The Quick Fix

Skip the auth check when running on the server:

beforeLoad: async () => {
  if (typeof window === 'undefined') return
  const { data } = await supabase.auth.getSession()
  if (!data.session) throw redirect({ to: '/login' })
},

typeof window === 'undefined' is true on the server, false in the browser. When it's server-side, return early. When it's client-side, check the session normally.

This stops the false redirect. The client hydrates, the session loads from localStorage, and the guard runs correctly in the browser.

The Proper Fix

The quick fix works, but it means your server-side rendering has no auth context. You can't pre-render protected content on the server. For a simple app that's fine. For anything more, you want the server to actually know who's logged in.

The right tool is @supabase/ssr. It replaces localStorage with cookies as the session store. Cookies are sent with every HTTP request — including server-side ones. So your SSR beforeLoad can call getSession() and get a real answer.

The migration involves replacing createClient from @supabase/supabase-js with createServerClient and createBrowserClient from @supabase/ssr, and wiring up cookie read/write handlers. More setup, but it's the correct mental model: the server should be a first-class participant in auth.

Apply It Both Ways

This guard needs to be on both routes — not just the protected route.

If you have a check on /dashboard that redirects unauthenticated users to /login, you probably also have a check on /login that redirects authenticated users away. Both of those beforeLoad functions run server-side. Both will get a null session. Both will redirect incorrectly without the fix.

Add the typeof window === 'undefined' guard to every route that makes an auth decision.

The Takeaway

The browser and server have different storage access. When your framework runs route guards on the server, any auth check that reads from localStorage will silently fail. The session isn't gone — it's just invisible to the server.

Short term: bail out early on the server. Long term: use cookies so the server can actually participate.

© 2026. All rights reserved