Vercel Logo

Supabase Client Utilities

Next.js apps run in two environments: the browser and the server. Each needs its own Supabase client with different cookie handling. The @supabase/ssr package provides SSR-compatible clients that keep auth state synchronized.

Outcome

Create browser and server Supabase clients that handle authentication cookies correctly in both environments.

Fast Track

  1. Implement utils/supabase/client.ts with createBrowserClient
  2. Implement utils/supabase/server.ts with createServerClient
  3. Verify no TypeScript errors in both files

Hands-on Exercise 1.3

Implement the Supabase client utilities (TODO stubs are provided):

Requirements:

  1. Implement the browser client using createBrowserClient
  2. Implement the server client using createServerClient with cookie handling
  3. Both files export a createSupabaseClient function
  4. Handle the async cookies API in the server client

Implementation hints:

  • The browser client is simple - just pass the URL and anon key
  • The server client needs cookie getAll and setAll callbacks
  • Use await cookies() in Next.js 16 (it's now async)
  • Wrap setAll in try/catch for Server Components

Try It

  1. Check the browser client:

    # Open the file and verify it exports createSupabaseClient
    cat utils/supabase/client.ts
  2. Check the server client:

    cat utils/supabase/server.ts
  3. Verify no TypeScript errors:

    pnpm build

    If the build succeeds (or only fails on missing Stripe config), your clients are correct.

  4. Test in dev:

    pnpm dev

    Visit http://localhost:3000 - the page should load without console errors about Supabase.

Commit

git add -A
git commit -m "feat(auth): add Supabase client utilities"

Done-When

  • utils/supabase/client.ts exports createSupabaseClient
  • utils/supabase/server.ts exports async createSupabaseClient
  • Server client handles cookie operations with getAll and setAll
  • No TypeScript errors in either file
  • Dev server runs without Supabase-related errors

Solution

Browser Client: utils/supabase/client.ts

Replace the TODO stub with the implementation:

utils/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
 
export function createSupabaseClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}

The browser client is straightforward. createBrowserClient handles cookies automatically using document.cookie.

Server Client: utils/supabase/server.ts

Replace the TODO stub with the implementation:

utils/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
 
export async function createSupabaseClient() {
  const cookieStore = await cookies();
 
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // The `setAll` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    }
  );
}

The server client is more involved:

  1. await cookies() - In Next.js 16, the cookies API is async
  2. getAll() - Returns all cookies for Supabase to read session data
  3. setAll() - Updates cookies when the session refreshes
  4. try/catch - Server Components can't set cookies directly; the proxy handles this

Why Two Clients?

EnvironmentCookie AccessWhen Used
Browserdocument.cookieClient Components, event handlers
Servercookies() APIServer Components, Server Actions, Route Handlers

Browser code can't access the cookies() API, and server code can't access document.cookie. Each client uses the appropriate method for its environment.

Here's how authentication cookies flow through the app:

Browser Request
    ↓
proxy.ts (refreshes session, sets cookies)
    ↓
Server Component (reads cookies via server client)
    ↓
HTML Response (includes Set-Cookie headers)
    ↓
Browser (stores cookies, uses browser client)

The proxy refreshes expired sessions on every request. Server Components read the current session. The browser stores updated cookies for future requests.

Troubleshooting

"cookies is not a function" error:

You're likely importing from the wrong package. Use:

import { cookies } from "next/headers";

Not:

// Wrong!
import { cookies } from "next/navigation";

"Cannot set cookies in Server Component" warning:

This is expected. The setAll try/catch handles this case. The proxy (proxy.ts) handles session refresh instead.

TypeScript errors about cookie types:

Make sure you have the latest @supabase/ssr version:

pnpm update @supabase/ssr