docs: add CDN caching warning for SSR auth middleware (#43575)

When a session token is refreshed server-side, `@supabase/ssr` writes
the updated JWT via Set-Cookie. If a CDN caches that response and serves
it to another user, that user will be signed in as the wrong person.

Adds documentation covering this in two places:
- creating-a-client.mdx: brief mention with a link to the full
explanation
- advanced-guide.mdx: expands the existing CDN FAQ with an explanation
of the risk and Cache-Control: private, no-store examples for Next.js
and Nuxt

Related: https://github.com/supabase/supabase-js/issues/1682

---------

Co-authored-by: Chris Chinchilla <chris.ward@supabase.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-03-11 09:17:33 +02:00
1 parent 0972d61db5
commit 4e2405aaad
6 files changed
+80 -18

No files matched your search

@@ -60,9 +60,59 @@ A good default is to use `Lax` which sends cookies when users are navigating to
### Can I use server-side rendering with a CDN or cache?
Yes, but you need to be careful to include at least the refresh token cookie value in the cache key. Otherwise you may be accidentally serving pages with data belonging to different users!
Yes, but there are two specific scenarios that can cause users to receive another user's session. Both are related to caching of HTTP responses that contain `Set-Cookie` headers.
Also be sure you set proper cache control headers. We recommend invalidating cache keys every hour or less.
#### ISR (incremental static regeneration)
If you use ISR on pages that trigger a Supabase session refresh, the cached response will include the `Set-Cookie` header containing the refreshed JWT. When that cached response is served to a subsequent user, their browser stores the token and they are signed in as the wrong person.
Do not enable ISR on any route where authentication is handled or where a session refresh can occur. In Nuxt, avoid setting `isr` on authenticated routes. In Next.js, use `export const dynamic = 'force-dynamic'` on pages that require authentication.
#### CDN and reverse proxy caching
When `@supabase/ssr` refreshes a session token server-side, it writes the updated JWT to the HTTP response via a `Set-Cookie` header. If your CDN (e.g. Vercel Edge, Cloudflare) caches that response and serves it to a different user, that user's browser will store the cached token and be signed in as the wrong person.
To prevent this, set `Cache-Control: private, no-store` on responses from any route that handles authentication, typically your middleware. Most CDNs respect this header and will not cache the response.
#### Next.js middleware
```ts
const response = NextResponse.next()
// ... supabase client setup and getUser() call
response.headers.set('Cache-Control', 'private, no-store')
return response
```
#### Nuxt server middleware
```ts
// ... supabase client setup and getUser() call
setHeader(event, 'Cache-Control', 'private, no-store')
```
**CloudFront**
CloudFront's behavior depends on its cache policy configuration and is not solely controlled by the `Cache-Control` response header. Even with `Cache-Control: private, no-store`, CloudFront can still cache the response and the `Set-Cookie` header if its cache policy has a Minimum TTL greater than 0, or if cookies and the `Set-Cookie` header are not forwarded to the origin.
To protect against session leakage on CloudFront, use one or more of the following steps:
- **Set Minimum TTL to 0** in your CloudFront cache policy. This allows `Cache-Control: no-store` to take effect as intended.
- **Use `Cache-Control: no-cache="Set-Cookie"`** to instruct CloudFront not to cache the `Set-Cookie` header specifically, while still allowing other parts of the response to be cached.
- **Disable caching entirely** for authenticated routes (e.g. your middleware path) by associating a cache policy with TTL set to 0, or by using the managed `CachingDisabled` policy for those behaviors.
<Admonition type="note">
On other managed CDN platforms (for example AWS Amplify), cache policies are configured at the platform level and may similarly not fully respect `Cache-Control` headers set in your application. Always verify your CDN's caching behavior for routes that set cookies.
</Admonition>
If you need to cache SSR pages for performance, apply caching only to routes that do not write `Set-Cookie` headers, and always include the refresh token cookie value in the cache key for any routes that serve user-specific content.
#### Vercel Fluid compute (in-memory client sharing)
Vercel's Fluid compute model can keep server instances warm and reuse them across requests. In some cases this means a Supabase client initialized in module scope — or stored in a shared variable — may be reused across requests from different users, causing one user's session to leak into another user's request.
Always initialize the Supabase client inside the request handler, not at module level. Do not store the client or any user-specific state in a variable that persists between requests.
### Which authentication flows have PKCE support?
@@ -744,6 +744,12 @@ language="typescript"
</TabPanel>
</Tabs>
## Caching considerations
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](/docs/guides/auth/server-side/advanced-guide#can-i-use-server-side-rendering-with-a-cdn-or-cache) for details and framework-specific examples.
## Next steps
- Implement [Authentication using Email and Password](/docs/guides/auth/passwords)
@@ -395,7 +395,10 @@ const OneTapComponent = () => {
console.log('Nonce: ', nonce, hashedNonce)
// check if there's already an existing session before initializing the one-tap UI
const { data: { claims }, error } = await supabase.auth.getClaims()
const {
data: { claims },
error,
} = await supabase.auth.getClaims()
if (error) {
console.error('Error getting claims', error)
}
+15 -15
View File
@@ -117,7 +117,7 @@ height={650}
| Component | Description |
| ------------------- | ------------------------------------------------------ |
| **Used** | RAM actively used by Postgres and the operating system |
| **Cache + buffers** | Memory used for page cache and OS buffers |
| **Cache + buffers** | Memory used for page cache and OS buffers |
| **Free** | Available unallocated memory |
How it helps debug issues:
@@ -225,13 +225,13 @@ How it helps debug issues:
Actions you can take:
| Action | Description |
| -------------------------------------------------------------- | ------------------------------------------------------------- |
| [Optimize disk-intensive queries](/docs/content/guides/database/query-optimization) | Reduce queries that perform excessive reads/writes |
| Tune caching and batching | Minimize repeated disk access and improve throughput headroom |
| [Upgrade compute size](/docs/guides/platform/compute-and-disk) | Increase throughput limits for sustained workloads |
| Review database design | Optimize schema and query patterns for efficiency |
| [Add strategic indexes](http://localhost:3001/docs/guides/database/postgres/indexes) | Reduce sequential scans with appropriate indexing |
| Action | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
| [Optimize disk-intensive queries](/docs/content/guides/database/query-optimization) | Reduce queries that perform excessive reads/writes |
| Tune caching and batching | Minimize repeated disk access and improve throughput headroom |
| [Upgrade compute size](/docs/guides/platform/compute-and-disk) | Increase throughput limits for sustained workloads |
| Review database design | Optimize schema and query patterns for efficiency |
| [Add strategic indexes](http://localhost:3001/docs/guides/database/postgres/indexes) | Reduce sequential scans with appropriate indexing |
### Disk size
@@ -245,7 +245,6 @@ src={{
light: '/docs/img/database/reports/disk-size-chart-light.png',
}}
/>
| Component | Description |
@@ -315,6 +314,7 @@ Actions you can take:
| Review application code | Ensure proper connection handling and cleanup |
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
### Dedicated Pooler (PgBouncer) Client Connections
Available on Team and Enterprise plans.
@@ -338,6 +338,7 @@ Actions you can take:
| Review application code | Ensure proper connection handling and cleanup |
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
### Shared Pooler (Supavisor) Client Connections
Available on Team and Enterprise plans.
@@ -367,14 +368,14 @@ Actions you can take:
<Image
alt="Disk Size chart"
zoomable
src={{
zoomable
src={{
dark: '/docs/img/database/reports/disk-size-chart-dark.png',
light: '/docs/img/database/reports/disk-size-chart-light.png',
}}
width={2148}
height={654}
width={2148}
height={654}
/>
| Component | Description |
@@ -411,7 +412,6 @@ The Edge Functions report provides insights into serverless function performance
| Edge Function Execution Time | Average function duration and performance | Performance optimization opportunities |
| Edge Function Invocations by Region | Geographic distribution of function calls | Global usage patterns and latency optimization |
## PostgREST
The PostgREST report provides insights into RESTful API performance, request patterns, and response characteristics.
@@ -429,7 +429,7 @@ The Realtime report tracks WebSocket connections, channel activity, and real-tim
| Chart | Description | Key Insights |
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------- |
| Connected Clients | Active WebSocket connections over time | Concurrent user activity and connection stability |
| Connected Clients | Active WebSocket connections over time | Concurrent user activity and connection stability |
| Broadcast Events | Broadcast events over time | Real-time feature usage patterns |
| Presence Events | Presence events over time | Real-time feature usage patterns |
| Postgres Changes Events | Postgres Changes events over time | Real-time feature usage patterns |
+1
View File
@@ -245,6 +245,7 @@ may_uppercase = [
"Vecs",
"Vector",
"Vercel",
"Vercel Fluid",
"Vercel Marketplace",
"Visual Studio Code",
"VM",
+2
View File
@@ -173,6 +173,7 @@ allow_list = [
"ClickHouse",
"Clippy",
"Cloudflare",
"CloudFront",
"codelab",
"Codium",
"Cognito",
@@ -257,6 +258,7 @@ allow_list = [
"Mansueli",
"Metabase",
"[Mm]in[Ii][Oo]",
"[Mm]itigations",
"Mixpeek",
"Multiplatform",
"MySQL",