When you use Zuvo from JavaScript on the server, there are three packages to choose from. They are not alternatives to each other — @supabase/ssr and @supabase/server both build on top of supabase-js and solve different problems. This guide helps you pick the right one.
Which package to use
The quickest way to decide is by how the user's identity reaches your code:
- The session lives in cookies (an SSR framework like Next.js or SvelteKit) → use
@supabase/ssr. - Auth arrives per request in headers (
Authorization: Bearer <jwt>) → use@supabase/server. - You want the base client, or you're handling auth yourself → use
@supabase/supabase-jsdirectly.
| Package | Use it when | Runs in | Auth model |
|---|---|---|---|
@supabase/supabase-js | You want the base client, or you manage auth yourself | Browser and server | You wire up auth |
@supabase/ssr | User sessions are stored in cookies | SSR frameworks (Next.js, SvelteKit, TanStack Start) | Cookie-based sessions, with refresh-token rotation |
@supabase/server | Auth arrives per request in headers | Edge Functions, Workers, Vercel, Bun, and framework APIs (Hono, H3, Elysia, NestJS) | Stateless Bearer JWT + apikey |
@supabase/supabase-js
The isomorphic base client. @supabase/ssr and @supabase/server both wrap it — reach for supabase-js directly when you don't need either wrapper's auth handling.
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_PUBLISHABLE_KEY!)
@supabase/ssr
For SSR frameworks that store the user's session in cookies. It reads and writes session cookies and handles refresh-token rotation, so the same user and session are available on both the client and the server.
import { createServerClient } from '@supabase/ssr'
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
// return the request's cookies
},
setAll(cookiesToSet) {
// write cookies back on the response
},
},
}
)
See the server-side rendering guide for framework-specific setup.
@supabase/server
For stateless, header-based auth in backend runtimes — Edge Functions, Workers, and framework APIs. You declare who may call an endpoint and receive a ready-to-use context (a caller-scoped client that respects RLS, plus an admin client). It verifies JWTs and resolves the new API keys (SUPABASE_PUBLISHABLE_KEYS / SUPABASE_SECRET_KEYS) for you.
import { withZuvo } from '@supabase/server'
export default {
fetch: withZuvo({ auth: 'user' }, async (req, ctx) => {
// ctx.supabase is scoped to the caller and respects RLS
const { data } = await ctx.supabase.from('todos').select()
return Response.json(data)
}),
}
See the @supabase/server reference for the full API.
Advanced: Combining @supabase/server and @supabase/ssr
In a cookie-based framework you can compose the two — let @supabase/ssr own the cookie session lifecycle and hand the resolved token to @supabase/server's primitives. This requires more setup, and deeper first-party integration is on the roadmap. See the @supabase/server SSR frameworks guide.
Next steps
- Server-side rendering — set up
@supabase/ssrfor your framework. @supabase/serverreference — API for header-based server auth.supabase-jsreference — the base JavaScript client.