Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Use Supabase with SvelteKit

To use Supabase with SvelteKit, install @supabase/supabase-js and @supabase/ssr, then create a Supabase client in your server hooks for SSR-compatible auth. Use SvelteKit's load functions to fetch data server-side, form actions for mutations, and the onAuthStateChange listener for client-side session tracking. The @supabase/ssr package handles cookie-based session management automatically.

What you'll learn

  • How to initialize the Supabase client for both server and browser in SvelteKit
  • How to set up server hooks for SSR-compatible auth session management
  • How to fetch data in SvelteKit load functions using the Supabase client
  • How to handle login and signup with SvelteKit form actions
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read15-20 minSupabase (all plans), @supabase/supabase-js v2+, @supabase/ssr v0.5+, SvelteKit 2+March 2026RapidDev Engineering Team
TL;DR

To use Supabase with SvelteKit, install @supabase/supabase-js and @supabase/ssr, then create a Supabase client in your server hooks for SSR-compatible auth. Use SvelteKit's load functions to fetch data server-side, form actions for mutations, and the onAuthStateChange listener for client-side session tracking. The @supabase/ssr package handles cookie-based session management automatically.

Setting Up Supabase with SvelteKit for SSR Auth and Data Fetching

SvelteKit is a full-stack framework that supports server-side rendering, form actions, and API routes. Integrating Supabase with SvelteKit requires using the @supabase/ssr package for cookie-based session management instead of localStorage. This tutorial covers creating server and browser Supabase clients, setting up hooks for automatic session handling, fetching data in load functions, and building auth flows with form actions.

Prerequisites

  • A SvelteKit project (created with npm create svelte@latest)
  • A Supabase project with API credentials
  • Node.js 18+ installed
  • Basic familiarity with SvelteKit's routing and load functions

Step-by-step guide

1

Install Supabase dependencies

Install the Supabase JavaScript client and the SSR helper package. The @supabase/ssr package provides cookie-based session management that works with SvelteKit's server-side rendering. Without it, auth sessions would only persist in localStorage which is not available during SSR.

typescript
1npm install @supabase/supabase-js @supabase/ssr

Expected result: Both packages are installed and your .env file contains your Supabase URL and anon key.

2

Create server and browser Supabase clients

Create two helper functions: one for the server-side client (uses cookies for session storage) and one for the browser client (manages cookies via the document). The server client is used in hooks, load functions, and form actions. The browser client is used in Svelte components for client-side operations.

typescript
1// src/lib/supabase/server.ts
2import { createServerClient } from '@supabase/ssr'
3import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
4import type { Cookies } from '@sveltejs/kit'
5
6export function createSupabaseServerClient(cookies: Cookies) {
7 return createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
8 cookies: {
9 getAll: () => cookies.getAll(),
10 setAll: (cookiesToSet) => {
11 cookiesToSet.forEach(({ name, value, options }) => {
12 cookies.set(name, value, { ...options, path: '/' })
13 })
14 },
15 },
16 })
17}
18
19// src/lib/supabase/browser.ts
20import { createBrowserClient } from '@supabase/ssr'
21import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
22
23export function createSupabaseBrowserClient() {
24 return createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY)
25}

Expected result: Two client factories are available: one for server contexts and one for browser contexts.

3

Set up server hooks for session management

SvelteKit hooks run on every server request. Create a handle hook that initializes the Supabase server client and attaches the session to the event.locals object. This makes the session available to all load functions and form actions without re-initializing the client each time. The hook also refreshes the session if the JWT has expired.

typescript
1// src/hooks.server.ts
2import { createSupabaseServerClient } from '$lib/supabase/server'
3import type { Handle } from '@sveltejs/kit'
4
5export const handle: Handle = async ({ event, resolve }) => {
6 event.locals.supabase = createSupabaseServerClient(event.cookies)
7
8 // Use getUser() for server-side verification (not getSession())
9 event.locals.safeGetSession = async () => {
10 const { data: { user }, error } = await event.locals.supabase.auth.getUser()
11 if (error || !user) {
12 return { session: null, user: null }
13 }
14 const { data: { session } } = await event.locals.supabase.auth.getSession()
15 return { session, user }
16 }
17
18 return resolve(event, {
19 filterSerializedResponseHeaders(name) {
20 return name === 'content-range' || name === 'x-supabase-api-version'
21 },
22 })
23}

Expected result: Every server request has a Supabase client and a safe session getter available on event.locals.

4

Fetch data in a load function

Use the Supabase client from event.locals in your page's server load function to fetch data. The load function runs on the server during SSR and on navigation. Because the session is managed through cookies, the client automatically sends the user's JWT with every request, so RLS policies are applied correctly.

typescript
1// src/routes/dashboard/+page.server.ts
2import type { PageServerLoad } from './$types'
3import { redirect } from '@sveltejs/kit'
4
5export const load: PageServerLoad = async ({ locals }) => {
6 const { session, user } = await locals.safeGetSession()
7
8 if (!session) {
9 throw redirect(303, '/login')
10 }
11
12 const { data: todos, error } = await locals.supabase
13 .from('todos')
14 .select('*')
15 .eq('user_id', user.id)
16 .order('created_at', { ascending: false })
17
18 return {
19 session,
20 user,
21 todos: todos ?? [],
22 }
23}

Expected result: The page receives the user's todos from Supabase, rendered server-side on first load.

5

Build a login form with SvelteKit form actions

Use SvelteKit's form actions to handle login server-side. Form actions run on the server, so they use the server Supabase client with cookie-based sessions. After a successful login, the session cookie is set automatically and the user is redirected. This works without any client-side JavaScript.

typescript
1// src/routes/login/+page.server.ts
2import type { Actions } from './$types'
3import { fail, redirect } from '@sveltejs/kit'
4
5export const actions: Actions = {
6 login: async ({ request, locals }) => {
7 const formData = await request.formData()
8 const email = formData.get('email') as string
9 const password = formData.get('password') as string
10
11 const { error } = await locals.supabase.auth.signInWithPassword({
12 email,
13 password,
14 })
15
16 if (error) {
17 return fail(400, { error: error.message, email })
18 }
19
20 throw redirect(303, '/dashboard')
21 },
22}

Expected result: Users can sign in via the form. On success, they are redirected to the dashboard with an active session.

6

Track auth state on the client

Create a root layout that initializes the browser Supabase client and listens for auth state changes. Use SvelteKit's invalidateAll() to re-run load functions when the session changes, keeping server and client state in sync. This handles scenarios like token refresh and sign-out from another tab.

typescript
1<!-- src/routes/+layout.svelte -->
2<script lang="ts">
3 import { invalidateAll } from '$app/navigation'
4 import { onMount } from 'svelte'
5 import { createSupabaseBrowserClient } from '$lib/supabase/browser'
6
7 const supabase = createSupabaseBrowserClient()
8
9 onMount(() => {
10 const { data: { subscription } } = supabase.auth.onAuthStateChange(() => {
11 invalidateAll()
12 })
13
14 return () => subscription.unsubscribe()
15 })
16</script>
17
18<slot />

Expected result: Auth state changes (login, logout, token refresh) automatically update the page data.

Complete working example

src/hooks.server.ts
1// src/hooks.server.ts
2// SvelteKit server hooks for Supabase auth session management
3
4import { createServerClient } from '@supabase/ssr'
5import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
6import type { Handle } from '@sveltejs/kit'
7
8export const handle: Handle = async ({ event, resolve }) => {
9 // Create a Supabase server client with cookie-based session storage
10 event.locals.supabase = createServerClient(
11 PUBLIC_SUPABASE_URL,
12 PUBLIC_SUPABASE_ANON_KEY,
13 {
14 cookies: {
15 getAll: () => event.cookies.getAll(),
16 setAll: (cookiesToSet) => {
17 cookiesToSet.forEach(({ name, value, options }) => {
18 event.cookies.set(name, value, { ...options, path: '/' })
19 })
20 },
21 },
22 }
23 )
24
25 // Safe session getter — uses getUser() for server-side verification
26 event.locals.safeGetSession = async () => {
27 const {
28 data: { user },
29 error,
30 } = await event.locals.supabase.auth.getUser()
31
32 if (error || !user) {
33 return { session: null, user: null }
34 }
35
36 const {
37 data: { session },
38 } = await event.locals.supabase.auth.getSession()
39
40 return { session, user }
41 }
42
43 return resolve(event, {
44 filterSerializedResponseHeaders(name) {
45 return name === 'content-range' || name === 'x-supabase-api-version'
46 },
47 })
48}

Common mistakes when using Supabase with SvelteKit

Why it's a problem: Using getSession() instead of getUser() for server-side auth checks

How to avoid: getSession() reads from cookies without verifying the JWT. Always use getUser() on the server, which makes an API call to verify the token is valid and not tampered with.

Why it's a problem: Using localStorage-based createClient instead of @supabase/ssr in a SvelteKit app

How to avoid: Install @supabase/ssr and use createServerClient for hooks/load functions and createBrowserClient for components. The standard createClient uses localStorage which is not available during SSR.

Why it's a problem: Forgetting to call invalidateAll() on auth state changes, causing stale data

How to avoid: Set up onAuthStateChange in your root layout and call invalidateAll() on every event. This re-runs load functions and updates the page with fresh session data.

Best practices

  • Use @supabase/ssr for cookie-based sessions that work with SvelteKit's server-side rendering
  • Always verify the user with getUser() on the server — never trust getSession() for authorization
  • Redirect unauthenticated users in load functions before fetching user-specific data
  • Use SvelteKit form actions for auth flows so they work without client-side JavaScript
  • Set up onAuthStateChange in the root layout to keep client and server state in sync
  • Store Supabase credentials in .env with the PUBLIC_ prefix for SvelteKit's environment variable system
  • Filter serialized response headers in hooks to prevent Supabase-specific headers from leaking

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a SvelteKit app with Supabase for auth and data. Show me how to set up @supabase/ssr for cookie-based sessions, create server hooks, fetch data in load functions, and build a login form with form actions.

Supabase Prompt

Set up Supabase with SvelteKit using @supabase/ssr. Create server hooks for session management, a server load function that fetches the user's todos with RLS, and a login form action. Make sure auth state changes trigger page data refresh.

Frequently asked questions

Why do I need @supabase/ssr instead of the regular Supabase client?

SvelteKit renders pages on the server where localStorage is not available. The @supabase/ssr package stores auth sessions in cookies, which are sent with every request and accessible during SSR.

How do I protect routes in SvelteKit with Supabase?

Check for a valid session in your page's +page.server.ts load function using locals.safeGetSession(). If there is no session, throw redirect(303, '/login') to send unauthenticated users to the login page.

Can I use Supabase Realtime with SvelteKit?

Yes. Initialize the browser Supabase client in your component and subscribe to channels using supabase.channel(). Realtime subscriptions are client-side only and work the same as in any other framework.

How do I handle OAuth redirects in SvelteKit?

Create an auth callback route (e.g., /auth/callback/+server.ts) that exchanges the auth code for a session. Set the redirectTo option in signInWithOAuth to point to this callback URL.

Do RLS policies work with SvelteKit's server-side rendering?

Yes. The @supabase/ssr server client reads the session from cookies and sends the JWT with every request. Supabase applies RLS policies based on this JWT, so server-side queries respect the same access rules as client-side queries.

How do I share the Supabase client across components?

Create the browser client once in the root layout and pass it to child components via Svelte's context API or stores. For server-side code, access the client from event.locals which is set up in hooks.server.ts.

Can RapidDev help integrate Supabase with my SvelteKit application?

Yes, RapidDev can set up the full Supabase integration for your SvelteKit app, including auth flows, data fetching patterns, RLS policies, and real-time subscriptions.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.