Logo

Fixing Cookie Loss When Proxying from Fastify → Next.js

nextjsfastify

Intro

Ran into a weird issue while proxying requests from Fastify into Next.js: Next.js won't preserve cookies when rendering pages via a custom server. Even if your backend (Fastify) handles cookies correctly, req.cookies and Set-Cookie headers disappear downstream.

What I ended up doing: store cookies temporarily on a custom property (called tempCookies) on the Fastify request, and then inject them back into the headers before calling Next.js' request handler. Feels scratch-your-head at first, but it works ⚙️

The Problem

When forwarding a request into Next.js using something like:

1await handle(req.raw, reply.raw)

Next.js ignores Fastify's req.cookies and even strips any Set-Cookie headers sent before it. Essentially:

  • Browser → Fastify Request
  • Fastify sets & reads cookies ok
  • Set-Cookie header ends up lost
  • In handle(req.raw,…) Next.js overrides headers

The Workaround (My Solution)

1. Extend FastifyRequest

Add a custom property to save cookies across the proxy boundary:

1declare module 'fastify' {
2 interface FastifyRequest {
3 tempCookies?: string[]
4 }
5}

2. In nextjsCustomServer.ts

Before forwarding to Next.js, re-attach the saved cookies:

1serve.all('/*', async (req, reply) => {
2 if (Array.isArray(req.tempCookies) && req.tempCookies.length > 0) {
3 reply.raw.setHeader('Set-Cookie', req.tempCookies)
4 }
5 await handle(req.raw, reply.raw)
6 reply.hijack()
7})

That ensures Next.js' HTTP response includes any cookie headers Fastify accumulated.

3. setCookie() helper logic (backend response → client propagation)

When Fastify receives a Set-Cookie header from another service or API, capture and replay it:

1export function setCookie(rawSetCookie: string, req: FastifyRequest) {
2 // Simplified for this note: parse key=value from raw header
3 const [whatToSet] = rawSetCookie.split(';')
4 req.tempCookies = (req.tempCookies ?? []).concat(rawSetCookie)
5
6 const [name, value] = whatToSet.split('=')
7 if (value === '' && req.cookies) {
8 delete req.cookies[name]
9 } else {
10 req.cookies = req.cookies || {}
11 req.cookies[name] = value
12 }
13
14 // Sync headers so Next.js can read them in SSR
15 const parsed = parse(req.headers.cookie ?? '')
16 value === '' ? delete parsed[name] : (parsed[name] = value)
17 req.headers.cookie = Object.entries(parsed)
18 .map(([k, v]) => `${k}=${v}`)
19 .join('; ')
20}

Now setCookie() does three things:

  1. Stores the original Set-Cookie header in req.tempCookies.
  2. Updates req.cookies object so Fastify continues logic near-term.
  3. Rewrites req.headers.cookie so Next.js' internal cookie parser sees the new or deleted cookie.

🤔 Why it happens & what docs say

  • Next.js re-constructs cookies for SSR based on the raw Node HTTP headers, not using Fastify's req.cookies.
  • You have to manually sync cookies if you use a custom server proxy.
  • Articles on header forwarding (e.g. Jon Meyers' blog) confirm: Next.js Server Components paired with Route Handlers won't forward cookies automatically—you must forward them in fetch() calls or wrapper layers

🧪 Caveats & gotchas

  • If tempCookies is large or you add duplicates repeatedly, it can bloat headers. You can dedupe by cookie name if needed.
  • If setting multiple cookies with different attributes, always append them to an array when calling setHeader('Set-Cookie', …).
  • Be cautious with domains, httpOnly, SameSite, etc—in local development your domain matching logic might differ.

✅ Summary

  • Next.js doesn't automatically propagate cookies when you use a custom server proxy.
  • Fastify's req.cookies and res.setHeader() must be translated into raw HTTP headers manually.
  • My workaround: store cookies in req.tempCookies, reattach before calling Next.js, and rewrite the cookie header.
  • This lets my Next.js SSR see the correct cookies for auth/session logic—and the browser gets the actual Set-Cookie instructions.

Now I can move cookies around the proxy boundary without them vanishing in thin air 😅