Cookies
@nano_kit/cookie-store provides a request-bound, CookieStore-compatible implementation for SSR and tests.
Use it when server-rendered code needs to read incoming cookies, write Set-Cookie headers, or provide cookie state through Nano Kit dependency injection. You can use it directly through the CookieStore API, or combine it with cookieStored from @nano_kit/platform-web when you want cookie-backed signals.
Installation
Section titled “Installation”@nano_kit/cookie-store is an optional peer dependency of @nano_kit/ssr. Install it only when your SSR app uses cookies.
pnpm add @nano_kit/cookie-storeyarn add @nano_kit/cookie-storenpm install @nano_kit/cookie-storeVite Plugin
Section titled “Vite Plugin”Enable request-bound cookie stores with the SSR plugin option:
// vite.config.tsimport { defineConfig } from 'vite'import react from '@vitejs/plugin-react'import ssr from '@nano_kit/react-ssr/vite-plugin'
export default defineConfig({ plugins: [ react(), ssr({ index: 'src/index.tsx', cookieStore: true }) ]})The option makes the renderer create a VirtualCookieStore for each request and provide it through CookieStore$.
Direct Cookie Store Usage
Section titled “Direct Cookie Store Usage”Read CookieStore$ inside a store factory when you want to work with the CookieStore API directly.
// src/stores/session.tsimport { action, inject } from '@nano_kit/store'import { CookieStore$ } from '@nano_kit/cookie-store'
const SESSION_MAX_AGE = 60 * 60 * 24 * 30
export function Session$() { const cookieStore = inject(CookieStore$) const getUsername = () => cookieStore.get('session') const login = action((username: string) => { const value = username.trim()
if (value) { void cookieStore.set({ name: 'session', value, path: '/', sameSite: 'lax', expires: Date.now() + SESSION_MAX_AGE * 1000 }) } }) const logout = action(() => { void cookieStore.delete({ name: 'session', path: '/' }) })
return { getUsername, login, logout }}In the browser, CookieStore$ resolves to the native browser cookieStore. During SSR, it resolves to a virtual store created from the incoming Cookie header for the current request.
Cookie-Backed Signals
Section titled “Cookie-Backed Signals”If you want a writable signal backed by cookies, pass the injected CookieStore$ value to cookieStored:
// src/stores/session.tsimport { action, inject } from '@nano_kit/store'import { CookieStore$ } from '@nano_kit/cookie-store'import { cookieStored } from '@nano_kit/platform-web'
const SESSION_MAX_AGE = 60 * 60 * 24 * 30
export function Session$() { const cookieStore = inject(CookieStore$) const $username = cookieStored<string | null>(cookieStore, { name: 'session', path: '/', sameSite: 'lax', maxAge: SESSION_MAX_AGE }, null) const login = action((username: string) => { const value = username.trim()
if (value) { $username(value) } }) const logout = action(() => { $username(null) })
return { $username, login, logout }}Production Server
Section titled “Production Server”Pass the incoming Cookie header to renderer.render(url, cookieHeader), and forward returned Set-Cookie headers to the HTTP response.
// server.jsimport { renderer } from './dist/renderer/index.js'
app.get('*', async (req, res) => { const result = await renderer.render(req.url, req.headers.cookie)
if (result.setCookieHeaders) { res.setHeader('Set-Cookie', result.setCookieHeaders) }
if (result.redirect) { return res.redirect(result.statusCode, result.redirect) }
if (result.html !== null) { return res.status(result.statusCode).send(result.html) }
res.status(result.statusCode).send('Not Found')})Server-Side Mutations
Section titled “Server-Side Mutations”Because cookies are available in Stores$, a route can mutate cookies on the server and redirect without rendering UI.
// src/pages/Logout.tsximport { Navigation$ } from '@nano_kit/router'import { inject } from '@nano_kit/store'import { Session$ } from '../stores/session'
export function Stores$() { const navigation = inject(Navigation$) const { logout } = inject(Session$)
logout() navigation.replace('/')
return []}
export default function Logout() { return <></>}The renderer returns a redirect and a deletion Set-Cookie header. The browser receives both in the same response.
Low-Level Usage
Section titled “Low-Level Usage”You can also use VirtualCookieStore directly in tests or custom render pipelines:
import { InjectionContext, provide } from '@nano_kit/store'import { CookieStore$, VirtualCookieStore} from '@nano_kit/cookie-store'
const cookieStore = new VirtualCookieStore( 'theme=dark; session=abc123', '/dashboard')
const context = new InjectionContext([ provide(CookieStore$, cookieStore)])
await cookieStore.set({ name: 'theme', value: 'light', path: '/', sameSite: 'lax'})
cookieStore.peek('theme') // 'light'cookieStore.drainSetCookieHeaders()// ['theme=light; Path=/; SameSite=Lax']Example
Section titled “Example”See the Session Cookies example for a complete React SSR app using request cookies, cookie-backed signals, server-side logout, and hydration without mismatches.