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
- Implement
utils/supabase/client.tswithcreateBrowserClient - Implement
utils/supabase/server.tswithcreateServerClient - Verify no TypeScript errors in both files
Hands-on Exercise 1.3
Implement the Supabase client utilities (TODO stubs are provided):
Requirements:
- Implement the browser client using
createBrowserClient - Implement the server client using
createServerClientwith cookie handling - Both files export a
createSupabaseClientfunction - 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
getAllandsetAllcallbacks - Use
await cookies()in Next.js 16 (it's now async) - Wrap
setAllin try/catch for Server Components
Try It
-
Check the browser client:
# Open the file and verify it exports createSupabaseClient cat utils/supabase/client.ts -
Check the server client:
cat utils/supabase/server.ts -
Verify no TypeScript errors:
pnpm buildIf the build succeeds (or only fails on missing Stripe config), your clients are correct.
-
Test in dev:
pnpm devVisit 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.tsexportscreateSupabaseClientutils/supabase/server.tsexports asynccreateSupabaseClient- Server client handles cookie operations with
getAllandsetAll - 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:
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:
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:
await cookies()- In Next.js 16, the cookies API is asyncgetAll()- Returns all cookies for Supabase to read session datasetAll()- Updates cookies when the session refreshes- try/catch - Server Components can't set cookies directly; the proxy handles this
Why Two Clients?
| Environment | Cookie Access | When Used |
|---|---|---|
| Browser | document.cookie | Client Components, event handlers |
| Server | cookies() API | Server 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.
Cookie Flow
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/ssrWas this helpful?