Demo, all content is generated
Question

After logout my header still shows the old user until I hard refresh

Solved · 982 views · asked by rosa_m · edited

Next.js 15 app router + Supabase. Logout button calls supabase.auth.signOut() and then router.push('/'). The homepage header (server component) still says 'Hi Rosa' with my avatar. If I press F5 it's gone. Worse: when my sister logged in on the same laptop she briefly saw my dashboard numbers before hers loaded.

What I’ve tried

Added export const dynamic = 'force-dynamic' to the layout. Cursor added a window.location.reload() after logout which works but looks terrible.

Comment
Is the logout done in a client component with the browser client? And is the header a server component? dev_ana · edited

3 answers

Marked as helpful by the asker
dev_ana · edited

Two layers are holding on to the old state: the Next.js client-side router cache (server components rendered earlier are reused on client navigation) and whatever client state you keep.

Cleanest fix: do the logout on the server and tell Next the rendered output is stale.

// app/actions/auth.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function logout() {
  const supabase = await createClient()
  await supabase.auth.signOut()
  revalidatePath('/', 'layout')
  redirect('/')
}

The button becomes <form action={logout}><button>Log out</button></form>.

If you keep a client-side logout, call router.refresh() after signOut() instead of only router.push. That refetches the server components with the new (empty) cookies.

Comment
Server action version works perfectly, no more reload. Thank you! rosa_m · edited
The 'sister saw my dashboard' part is why this matters. It's not just cosmetic. aiko_n · edited
rafa_dev · edited

force-dynamic doesn't help here because the stale copy is in the browser's router cache, not on the server. That's why only a refresh (or router.refresh()) fixes it. Also check your <Link>s to protected pages: prefetched versions can be served from that cache right after logout.

Comment
That explains why force-dynamic did nothing. Thanks. rosa_m · edited
mei_lin · edited

Late, but for anyone landing here from search: also clear client-side caches on logout (React Query queryClient.clear(), zustand stores, etc.), e.g. in an onAuthStateChange listener for SIGNED_OUT. Otherwise the next person on the same browser can briefly see the previous user's data, which is exactly the sister scenario.

Comment