# How to Use Supabase with SvelteKit

- Tool: Supabase
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Supabase (all plans), @supabase/supabase-js v2+, @supabase/ssr v0.5+, SvelteKit 2+
- Last updated: March 2026

## 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.

## Before you start

- 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.

```
npm 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.

```
// src/lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import type { Cookies } from '@sveltejs/kit'

export function createSupabaseServerClient(cookies: Cookies) {
  return createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
    cookies: {
      getAll: () => cookies.getAll(),
      setAll: (cookiesToSet) => {
        cookiesToSet.forEach(({ name, value, options }) => {
          cookies.set(name, value, { ...options, path: '/' })
        })
      },
    },
  })
}

// src/lib/supabase/browser.ts
import { createBrowserClient } from '@supabase/ssr'
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'

export function createSupabaseBrowserClient() {
  return createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY)
}
```

**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.

```
// src/hooks.server.ts
import { createSupabaseServerClient } from '$lib/supabase/server'
import type { Handle } from '@sveltejs/kit'

export const handle: Handle = async ({ event, resolve }) => {
  event.locals.supabase = createSupabaseServerClient(event.cookies)

  // Use getUser() for server-side verification (not getSession())
  event.locals.safeGetSession = async () => {
    const { data: { user }, error } = await event.locals.supabase.auth.getUser()
    if (error || !user) {
      return { session: null, user: null }
    }
    const { data: { session } } = await event.locals.supabase.auth.getSession()
    return { session, user }
  }

  return resolve(event, {
    filterSerializedResponseHeaders(name) {
      return name === 'content-range' || name === 'x-supabase-api-version'
    },
  })
}
```

**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.

```
// src/routes/dashboard/+page.server.ts
import type { PageServerLoad } from './$types'
import { redirect } from '@sveltejs/kit'

export const load: PageServerLoad = async ({ locals }) => {
  const { session, user } = await locals.safeGetSession()

  if (!session) {
    throw redirect(303, '/login')
  }

  const { data: todos, error } = await locals.supabase
    .from('todos')
    .select('*')
    .eq('user_id', user.id)
    .order('created_at', { ascending: false })

  return {
    session,
    user,
    todos: todos ?? [],
  }
}
```

**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.

```
// src/routes/login/+page.server.ts
import type { Actions } from './$types'
import { fail, redirect } from '@sveltejs/kit'

export const actions: Actions = {
  login: async ({ request, locals }) => {
    const formData = await request.formData()
    const email = formData.get('email') as string
    const password = formData.get('password') as string

    const { error } = await locals.supabase.auth.signInWithPassword({
      email,
      password,
    })

    if (error) {
      return fail(400, { error: error.message, email })
    }

    throw redirect(303, '/dashboard')
  },
}
```

**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.

```
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { invalidateAll } from '$app/navigation'
  import { onMount } from 'svelte'
  import { createSupabaseBrowserClient } from '$lib/supabase/browser'

  const supabase = createSupabaseBrowserClient()

  onMount(() => {
    const { data: { subscription } } = supabase.auth.onAuthStateChange(() => {
      invalidateAll()
    })

    return () => subscription.unsubscribe()
  })
</script>

<slot />
```

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

## Complete code example

File: `src/hooks.server.ts`

```typescript
// src/hooks.server.ts
// SvelteKit server hooks for Supabase auth session management

import { createServerClient } from '@supabase/ssr'
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import type { Handle } from '@sveltejs/kit'

export const handle: Handle = async ({ event, resolve }) => {
  // Create a Supabase server client with cookie-based session storage
  event.locals.supabase = createServerClient(
    PUBLIC_SUPABASE_URL,
    PUBLIC_SUPABASE_ANON_KEY,
    {
      cookies: {
        getAll: () => event.cookies.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) => {
            event.cookies.set(name, value, { ...options, path: '/' })
          })
        },
      },
    }
  )

  // Safe session getter — uses getUser() for server-side verification
  event.locals.safeGetSession = async () => {
    const {
      data: { user },
      error,
    } = await event.locals.supabase.auth.getUser()

    if (error || !user) {
      return { session: null, user: null }
    }

    const {
      data: { session },
    } = await event.locals.supabase.auth.getSession()

    return { session, user }
  }

  return resolve(event, {
    filterSerializedResponseHeaders(name) {
      return name === 'content-range' || name === 'x-supabase-api-version'
    },
  })
}
```

## Common mistakes

- **Using getSession() instead of getUser() for server-side auth checks** — undefined Fix: 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.
- **Using localStorage-based createClient instead of @supabase/ssr in a SvelteKit app** — undefined Fix: 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.
- **Forgetting to call invalidateAll() on auth state changes, causing stale data** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-use-supabase-with-sveltekit
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-use-supabase-with-sveltekit
