S
Supabase•2y ago
Budi

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?
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from "$env/static/public"
import type { Database } from "$lib/types/supabase"
import { createBrowserClient, isBrowser, parse } from "@supabase/ssr"
import type { LayoutLoad } from "./$types"

export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends("supabase:auth")

const supabase = createBrowserClient<Database>(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch
},
cookies: {
get(key) {
if (!isBrowser()) {
return JSON.stringify(data.session)
}

const cookie = parse(document.cookie)
return cookie[key]
}
}
})

// // Isn't this redundant since the session is already provided by the server? Not making this call saves me a db interaction.
// const {
// data: { session }
// } = await supabase.auth.getSession()

// The last load function has priority over all others, so I need to pass the server load data through the layout load data for the page to access it.
return { supabase, session: data.session, posts: data.posts, message: data.message }
}
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from "$env/static/public"
import type { Database } from "$lib/types/supabase"
import { createBrowserClient, isBrowser, parse } from "@supabase/ssr"
import type { LayoutLoad } from "./$types"

export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends("supabase:auth")

const supabase = createBrowserClient<Database>(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch
},
cookies: {
get(key) {
if (!isBrowser()) {
return JSON.stringify(data.session)
}

const cookie = parse(document.cookie)
return cookie[key]
}
}
})

// // Isn't this redundant since the session is already provided by the server? Not making this call saves me a db interaction.
// const {
// data: { session }
// } = await supabase.auth.getSession()

// The last load function has priority over all others, so I need to pass the server load data through the layout load data for the page to access it.
return { supabase, session: data.session, posts: data.posts, message: data.message }
}
Creating a Supabase client for SSR | Supabase Docs
Configure Supabase client to use Cookies
17 Replies
garyaustin
garyaustin•2y ago
getSession does not make a server call UNLESS the session needs refreshing which is why I presume they use that.
Budi
BudiOP•2y ago
In what scenario would a session need refreshing?
garyaustin
garyaustin•2y ago
When the jwt is about to expire.
Budi
BudiOP•2y ago
in +layout.server.ts the session comes from calling getSession() too so isn't this redundant?
garyaustin
garyaustin•2y ago
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.
Budi
BudiOP•2y ago
I see. That's helpful, thanks! Trying to grok this Supabase SSR codebase and want to understand all parts 🙂
garyaustin
garyaustin•2y ago
It is complex. I don't use it though myself so only familiar with it from helping here.
francis
francis•2y ago
@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...
francis
francis•2y ago
@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)
Budi
BudiOP•2y ago
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?
Budi
BudiOP•2y ago
@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
Budi
BudiOP•2y ago
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.
francis
francis•2y ago
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.
Budi
BudiOP•2y ago
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...
francis
francis•2y ago
@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 invalidated
the 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.
francis
francis•2y ago
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#L1895
GitHub
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.
francis
francis•2y ago
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 running
are 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.

Did you find this page helpful?