apps/docs/content/guides/auth/server-side/creating-a-client.mdx
To use Server-Side Rendering (SSR) with Supabase, you need to configure your Supabase client to use cookies. The @supabase/ssr package helps you do this for JavaScript/TypeScript applications.
Install the @supabase/supabase-js and @supabase/ssr helper packages:
npm install @supabase/supabase-js @supabase/ssr
yarn add @supabase/supabase-js @supabase/ssr
pnpm add @supabase/supabase-js @supabase/ssr
Create a .env.local file in the project root directory. In the file, set the project's Supabase URL and Key:
<$Partial path="api_settings_steps.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} />
<Tabs scrollable size="small" type="underlined" defaultActiveId="nextjs" queryGroup="framework"> <TabPanel id="nextjs" label="Next.js">NEXT_PUBLIC_SUPABASE_URL=supabase_project_url
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key
PUBLIC_SUPABASE_URL=supabase_project_url
PUBLIC_SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key
PUBLIC_SUPABASE_URL=supabase_project_url
PUBLIC_SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key
SUPABASE_URL=supabase_project_url
SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key
SUPABASE_URL=supabase_project_url
SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key
SUPABASE_URL=supabase_project_url
SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key
Install dotenv:
npm i dotenv
And initialize it:
<Tabs size="small" type="underlined" queryGroup="package-manager" defaultActiveId="npm"> <TabPanel id="npm" label="npm">npm install dotenv
yarn add dotenv
pnpm add dotenv
SUPABASE_URL=supabase_project_url
SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key
You need setup code to configure a Supabase client to use cookies. Once you have the utility code, you can use the createClient utility functions to get a properly configured Supabase client.
Use the browser client in code that runs on the browser, and the server client in code that runs on the server.
<Tabs scrollable size="small" type="underlined" defaultActiveId="nextjs" queryGroup="framework"
<TabPanel id="nextjs" label="Next.js">
To access Supabase from a Next.js app, you need 2 types of Supabase clients:
Since Next.js Server Components can't write cookies, you need a Proxy to refresh expired Auth tokens and store them.
The Proxy is responsible for:
supabase.auth.getClaims().request.cookies.set.response.cookies.set.<AccordionItem
header={<span className="text-foreground">What does the `cookies` object do?</span>}
id="utility-cookies"
>
The cookies object lets the Supabase client know how to access the cookies, so it can read and write the user session data. To make `@supabase/ssr` framework-agnostic, the cookies methods aren't hard-coded. These utility functions adapt `@supabase/ssr`'s cookie handling for Next.js.
`setAll` is called whenever the library needs to write cookies, for example after a token refresh. It receives two arguments: the array of cookies to set, and a `headers` object containing cache headers (`Cache-Control`, `Expires`, `Pragma`) that must be applied to the HTTP response to prevent CDNs from caching the response and leaking the session to other users. In the Proxy, apply these headers to the response. In Server Components, the headers cannot be set, which is why the `setAll` call is wrapped in a try/catch and the error is ignored. The Proxy handles writing cookies and headers on every request.
The cookie is named `sb-<project_ref>-auth-token` by default.
</AccordionItem>
<AccordionItem
header={<span className="text-foreground">Do I need to create a new client for every route?</span>}
id="client-deduplication"
>
Yes! Creating a Supabase client is lightweight.
- On the server, it basically configures a `fetch` call. You need to reconfigure the fetch call anew for every request to your server, because you need the cookies from the request.
- On the client, `createBrowserClient` already uses a singleton pattern, so you only ever create one instance, no matter how many times you call your `createClient` function.
</AccordionItem>
Create a lib/supabase folder at the root of your project, or inside the ./src folder if you are using one, with a file for each type of client. Then copy the lib utility functions for each client type.
The code adds a matcher so the Proxy doesn't run on routes that don't access Supabase.
<Admonition type="danger">Be careful when protecting pages. The server gets the user session from the cookies, which can be spoofed by anyone.
Always use supabase.auth.getClaims() to protect pages and user data.
Never trust supabase.auth.getSession() inside server code such as Proxy. It isn't guaranteed to revalidate the Auth token.
It's safe to trust getClaims() because it validates the JWT signature against the project's published public keys every time.
You're done! To recap, you've successfully:
You can now use any Supabase features from your client or server code!
</TabPanel> <TabPanel id="sveltekit" label="SvelteKit">Set up server-side hooks in src/hooks.server.ts. The hooks:
<$CodeSample path="/auth/sveltekit/src/hooks.server.ts" meta="name=src/hooks.server.ts" language="typescript" />
To prevent TypeScript errors, add type definitions for the new event.locals properties.
<$CodeSample path="/auth/sveltekit/src/app.d.ts" meta="name=src/app.d.ts" language="typescript" />
Create a Supabase client in your root +layout.ts. This client can be used to access Supabase from the client or the server. In order to get access to the Auth token on the server, use a +layout.server.ts file to pass in the session from event.locals.
Page components can access the Supabase client from the data object using the load function.
<$CodeTabs> <$CodeSample path="/auth/sveltekit/src/routes/+layout.ts" meta="name=src/routes/+layout.ts" language="typescript" />
<$CodeSample path="/auth/sveltekit/src/routes/+layout.server.ts" meta="name=src/routes/+layout.server.ts" language="typescript" /> </$CodeTabs>
You're done! To recap, you've successfully:
You can now use any Supabase features from your client or server code!
</TabPanel> <TabPanel id="astro" label="Astro">By default, Astro apps are static. This means the requests for data happen at build time, rather than when the user requests a page. At build time, there is no user, session or cookies. Therefore, we need to configure Astro for Server-side Rendering (SSR) if you want data to be fetched dynamically per request.
import { defineConfig } from 'astro/config'
export default defineConfig({
output: 'server',
})
<Tabs scrollable size="small" type="underlined" defaultActiveId="astro-server" queryGroup="environment"
<TabPanel id="astro-server" label="Server">
---
import { createServerClient, parseCookieHeader } from "@supabase/ssr";
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
{
cookies: {
getAll() {
return parseCookieHeader(Astro.request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, headers) {
cookiesToSet.forEach(({ name, value }) =>
Astro.cookies.set(name, value))
Object.entries(headers).forEach(([key, value]) =>
Astro.response.headers.set(key, value)
)
},
},
}
);
---
<script>
import { createBrowserClient } from "@supabase/ssr";
const supabase = createBrowserClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY
);
</script>
import { createServerClient, parseCookieHeader } from "@supabase/ssr";
import type { APIContext } from "astro";
export async function GET(context: APIContext) {
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
{
cookies: {
getAll() {
return parseCookieHeader(context.request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, _headers) {
cookiesToSet.forEach(({ name, value }) =>
context.cookies.set(name, value))
},
},
}
);
return ...
}
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
import { defineMiddleware } from 'astro:middleware'
export const onRequest = defineMiddleware(async (context, next) => {
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
{
cookies: {
getAll() {
return parseCookieHeader(context.request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, _headers) {
cookiesToSet.forEach(({ name, value }) => context.cookies.set(name, value))
},
},
}
)
return next()
})
You can now use any Supabase features from your client or server code!
</TabPanel> <TabPanel id="remix" label="Remix"><Tabs scrollable size="small" type="underlined" defaultActiveId="remix-loader" queryGroup="environment"
<TabPanel id="remix-loader" label="Loader">
import { type LoaderFunctionArgs } from '@remix-run/node'
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
export async function loader({ request }: LoaderFunctionArgs) {
const responseHeaders = new Headers()
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, cacheHeaders) {
cookiesToSet.forEach(({ name, value, options }) =>
responseHeaders.append('Set-Cookie', serializeCookieHeader(name, value, options))
)
Object.entries(cacheHeaders).forEach(([key, value]) => responseHeaders.set(key, value))
},
},
}
)
return new Response('...', {
headers: responseHeaders,
})
}
import { type ActionFunctionArgs } from '@remix-run/node'
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
export async function action({ request }: ActionFunctionArgs) {
const responseHeaders = new Headers()
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, cacheHeaders) {
cookiesToSet.forEach(({ name, value, options }) =>
responseHeaders.append('Set-Cookie', serializeCookieHeader(name, value, options))
)
Object.entries(cacheHeaders).forEach(([key, value]) => responseHeaders.set(key, value))
},
},
}
)
return new Response('...', {
headers: responseHeaders,
})
}
import { type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { createBrowserClient } from "@supabase/ssr";
export async function loader({}: LoaderFunctionArgs) {
return {
env: {
SUPABASE_URL: process.env.SUPABASE_URL!,
SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY!,
},
};
}
export default function Index() {
const { env } = useLoaderData<typeof loader>();
const supabase = createBrowserClient(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY);
return ...
}
You can now use any Supabase features from your client or server code!
</TabPanel> <TabPanel id="react-router" label="React Router"><Tabs scrollable size="small" type="underlined" defaultActiveId="react-router-loader" queryGroup="environment"
<TabPanel id="react-router-loader" label="Loader">
import { LoaderFunctionArgs } from 'react-router'
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
export async function loader({ request }: LoaderFunctionArgs) {
const responseHeaders = new Headers()
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, cacheHeaders) {
cookiesToSet.forEach(({ name, value }) =>
responseHeaders.append('Set-Cookie', serializeCookieHeader(name, value))
)
Object.entries(cacheHeaders).forEach(([key, value]) => responseHeaders.set(key, value))
},
},
})
return new Response('...', {
headers: responseHeaders,
})
}
import { type ActionFunctionArgs } from '@react-router'
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
export async function action({ request }: ActionFunctionArgs) {
const responseHeaders = new Headers()
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, cacheHeaders) {
cookiesToSet.forEach(({ name, value }) =>
responseHeaders.append('Set-Cookie', serializeCookieHeader(name, value))
)
Object.entries(cacheHeaders).forEach(([key, value]) => responseHeaders.set(key, value))
},
},
})
return new Response('...', {
headers: responseHeaders,
})
}
import { type LoaderFunctionArgs } from "react-router";
import { useLoaderData } from "react-router";
import { createBrowserClient } from "@supabase/ssr";
export async function loader({}: LoaderFunctionArgs) {
return {
env: {
SUPABASE_URL: process.env.SUPABASE_URL!,
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!,
},
};
}
export default function Index() {
const { env } = useLoaderData<typeof loader>();
const supabase = createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY);
return ...
}
You can now use any Supabase features from your client or server code!
</TabPanel> <TabPanel id="express" label="Express"><Tabs scrollable size="small" type="underlined" defaultActiveId="server-client" queryGroup="environment"
<TabPanel id="server-client" label="Server Client">
const { createServerClient, parseCookieHeader, serializeCookieHeader } = require('@supabase/ssr')
exports.createClient = (context) => {
return createServerClient(process.env.SUPABASE_URL, process.env.SUPABASE_PUBLISHABLE_KEY, {
cookies: {
getAll() {
return parseCookieHeader(context.req.headers.cookie ?? '')
},
setAll(cookiesToSet, headers) {
cookiesToSet.forEach(({ name, value }) =>
context.res.appendHeader('Set-Cookie', serializeCookieHeader(name, value))
)
Object.entries(headers).forEach(([key, value]) => context.res.setHeader(key, value))
},
},
})
}
const express = require("express")
const dotenv = require("dotenv")
const { createClient } = require("./lib/supabase")
const app = express()
app.post("/hello-world", async function (req, res, next) {
const { email, emailConfirm } = req.body
...
const supabase = createClient({ req, res })
})
You can now use any Supabase features from your client or server code!
</TabPanel> <TabPanel id="hono" label="Hono"><Tabs scrollable size="small" type="underlined" defaultActiveId="server-client" queryGroup="environment"
<TabPanel id="server-client" label="Server Client">
Create a Hono middleware that creates a Supabase client.
<$CodeSample path="/auth/hono/src/middleware/auth.middleware.ts" meta="name=src/middleware/auth.middleware.ts" language="typescript" />
</TabPanel> <TabPanel id="hono-route" label="Route">You can now use this middleware in your Hono application to create a server Supabase client that can be used to make authenticated requests.
<$CodeSample path="/auth/hono/src/index.tsx" meta="name=src/index.tsx" language="typescript" />
</TabPanel> </Tabs> </TabPanel> </Tabs>If your app uses ISR (Incremental Static Regeneration) or is deployed behind a CDN, caching of HTTP responses can cause users to receive another user's session. When a session is refreshed, the new token is written to the response via Set-Cookie. If that response is cached and served to a different user, that user will be signed in as the wrong person.
See the advanced Auth server-side rendering guide for details and framework-specific examples.