Why get session again in `+layout.ts` for SvelteKit SSR?
In the docs for SSR, it's shown that in the
root layout code
(+layout.ts
) we need to grab the session again by calling the Supabase servers.
See https://supabase.com/docs/guides/auth/server-side/creating-a-client?framework=sveltekit&environment=layout line 24-26. But the session is already available to the layout from +layout.server.ts
, which load data is being input as an argument into the +layout.ts
layout load function. Isn't it better to do this instead?
Creating a Supabase client for SSR | Supabase Docs
Configure Supabase client to use Cookies
17 Replies
getSession does not make a server call UNLESS the session needs refreshing which is why I presume they use that.
In what scenario would a session need refreshing?
When the jwt is about to expire.
in
+layout.server.ts
the session comes from calling getSession() too
so isn't this redundant?Sorry I know nothing about that code base. Just I would not be concerned with multiple getSession calls as they normally just get the session from the supabase object in memory.
I see. That's helpful, thanks!
Trying to grok this Supabase SSR codebase and want to understand all parts 🙂
It is complex.
I don't use it though myself so only familiar with it from helping here.
@Budi if it helps, I tried to put together a best practices starter at https://github.com/fnimick/sveltekit-supabase-auth-starter . It includes a few things left out of the supabase docs, like proactive refresh of all session- and auth-dependent loaders when a session change is detected
GitHub
GitHub - fnimick/sveltekit-supabase-auth-starter: A starter templat...
A starter template for Sveltekit v2 with Supabase Auth used for authentication - GitHub - fnimick/sveltekit-supabase-auth-starter: A starter template for Sveltekit v2 with Supabase Auth used for au...
@Budi you are correct, there are two session calls made on first startup; however, future session invalidations will only invalidate the universal load function, not the server load function, so there won't be a round trip to the server every time the session is refreshed. (in addition, this means that the server load functions that depend on the parent will not also be re-run unnecessarily)
This is very helpful. I'm going through it now. Thank you for sharing this!
Are the two session calls made when you instantiate the browser and server client in the load functions?
So if you call
supabase.auth.signOut()
the SIGNED_OUT event only acts on the universal load function in /layout.ts
and not the server layout load
?@francis would you be so kind to help me understand how you've set up your
src/routes/+layout.svelte
here? (https://github.com/fnimick/sveltekit-supabase-auth-starter/blob/main/src/routes/%2Blayout.svelte)
Why are you using a store to hold the Supabase client? Isn't it already available through the app via the load functions, via either export let data
and $page.data
?
In my src/routes/+layout.svelte
I had defined the onAuthStateChange
monitor as follows: https://github.com/brucey0x/bv_markdown_blog/blob/ba8338beaf07058ffc837865388a1ceccb4e5202/src/routes/%2Blayout.svelte
However this doesn't appear to be working. When I'm calling supabase.auth.signOut()
the SIGNED_OUT event isn't triggering (and thus not causing a page refresh because the session ended). https://github.com/brucey0x/bv_markdown_blog/blob/ba8338beaf07058ffc837865388a1ceccb4e5202/src/routes/auth/%2Bpage.server.ts
Do you understand why that's happening?GitHub
sveltekit-supabase-auth-starter/src/routes/+layout.svelte at main ·...
A starter template for Sveltekit v2 with Supabase Auth used for authentication - fnimick/sveltekit-supabase-auth-starter
GitHub
bv_markdown_blog/src/routes/+layout.svelte at ba8338beaf07058ffc837...
Personal blog with Markdown posts hosted in external database. Supabase for Postgres, Auth, Storage with Drizzle ORM, Shadcn-Svelte, Tailwind CSS. - brucey0x/bv_markdown_blog
GitHub
bv_markdown_blog/src/routes/auth/+page.server.ts at ba8338beaf07058...
Personal blog with Markdown posts hosted in external database. Supabase for Postgres, Auth, Storage with Drizzle ORM, Shadcn-Svelte, Tailwind CSS. - brucey0x/bv_markdown_blog
Also I see you're defining the
onAuthStateChange
manager differently. I don't fully understand your implementation -- why have you set it up in this way? What's the benefit of the supabaseStore and why are you setting it to the subscription
? I see it's not being used elsewhere but in that src/routes/+layout.svelte
.
I've now refactored my src/routes/+layout.svelte
to reflect your implementation @francis but it's not resolving how the signout is being handled unfortunately. Would love your input on why that might be.sure, the reason is that a session update triggers a re-run of the load function, and updates the supabase client available through
page.data
(via invalidate('supabase:auth')
the problem is that the subscription to supabase.onAuthStateChange
is not attached to the new supabase client when the client returned from data.supabase
changes
so we update a store with the supabase client whenever data.supabase
changes so we can then manage the subscription (unsubscribe from the old client, subscribe to the new client) whenever the client changes as a result of the loader re-running.'
oh @Budi I didn't scroll down. This is very easy - the reason is that you are calling supabase.auth.signOut()
on the server client, which doesn't update the browser client in memory at all. what should be happening is that all loaders are invalidated, the root layout loaders re-run, and you get a new supabase client reference in your root layout with no session. The callback should not run in this case.
I see you're using use:enhance
for the form, which should call invalidateAll on success. I would suggest adding logging in your root layout server and universal load functions to ensure they're being rerun as you expect after the form POST succeeds.Hey @francis thanks for taking the time to answer.
I see so the browser client is invalidated via
depends("supabase:auth")
in src/routes/+layout.ts
, which is called via the subscription that we've set to renew whenever the supabaseStore
changes in src/routes/+layout.svelte
because it's a derived store which triggers upon update of the reference store.
But in that case, how is the server client invalidated? I'm initializing this in src/hooks.server.ts
but I'm not using an invalidation script there. I do note that you've said that calling supabase.auth.signOut()
logs out the server client.
I have refactored my code, which you can see here: https://github.com/brucey0x/bv_markdown_blog
When I now call the form, it's not forcing a page refresh, but if I redirect to a different page then it does result in forcing a browser refresh the next time a user visits the /admin
route. That's still different than the login though, which while logging in via the /admin
route forces a page refresh via the redirect in src/routes/auth/callback/+server.ts
.
I have logged the load functions upon submitting the signout form and you're right, src/routes/+layout.ts
isn't running. Any idea how I could resolve that?GitHub
GitHub - brucey0x/bv_markdown_blog: Personal blog with Markdown pos...
Personal blog with Markdown posts hosted in external database. Supabase for Postgres, Auth, Storage with Drizzle ORM, Shadcn-Svelte, Tailwind CSS. - GitHub - brucey0x/bv_markdown_blog: Personal blo...
@Budi feel free to ping me if you want to debug this live, I don't check question discords often.
how is the server client invalidatedthe server client doesn't need to be invalidated, since there is nothing persistent on the server side once the request is handled. on any future request, the client is recreated in the hooks 😉 in particular, when I said you are "logging out the server client", what was actually happening was the POST request was going through the hooks, a new client was created and attached to it using the session from the cookie, and that client was immediately logged out. (which will log out all other clients using that session, but critically, the browser client is not proactively notified or anything, since no mechanism exists for that)
When I now call the form, it's not forcing a page refresh, but if I redirect to a different page then it does result in forcing a browser refresh the next time a user visits the /admin route.Is the form that posts in
admin/+page.svelte
? If so, I wonder if it's due to the redirect.it should be calling
invalidateAll
on redirect:
the redirect from use:enhance
calls applyAction
: https://github.com/sveltejs/kit/blob/main/packages/kit/src/runtime/app/forms.js#L107C8-L107C8
applyAction
calls goto with invalidation: https://github.com/sveltejs/kit/blob/main/packages/kit/src/runtime/client/client.js#L1895GitHub
kit/packages/kit/src/runtime/client/client.js at main · sveltejs/kit
web development, streamlined. Contribute to sveltejs/kit development by creating an account on GitHub.
GitHub
kit/packages/kit/src/runtime/app/forms.js at main · sveltejs/kit
web development, streamlined. Contribute to sveltejs/kit development by creating an account on GitHub.
it should not force a page refresh, but it absolutely should be running your universal loader again
I have logged the load functions upon submitting the signout form and you're right, src/routes/+layout.ts isn't runningare you 100% sure? it should have invalidated all loaders. Remember that once the browser bundle is hydrated, the log of the load functions will happen in the browser console, not your terminal invalidating all does not force a page refresh, it causes all loaders to refresh data, but it doesn't trigger a browser level refresh of the whole page. if you display the current value of the session object in your root layout template, I'm pretty sure you'll see it update and be correctly cleared when the logout is called. actually, it might not be, since from supabase docs:
Since Supabase Auth uses JWTs for authentication, the access token JWT will be valid until it's expired. When the user signs out, Supabase revokes the refresh token and deletes the JWT from the client-side. This does not revoke the JWT and it will still be valid until it expires.so the access token JWT will still be valid, and your client side session will still exist, but the refresh will fail. you need to actually sign out the client side from the browser bundle.