Documentation Index
Fetch the complete documentation index at: https://mintlify.com/cloudflare/vinext/llms.txt
Use this file to discover all available pages before exploring further.
The next/headers module provides functions for accessing request headers and cookies in Server Components, Route Handlers, and Server Actions.
Import
import { headers, cookies, draftMode } from 'next/headers'
Returns a read-only Headers instance for the incoming request.
import { headers } from 'next/headers'
export default async function Page() {
const headersList = await headers()
const userAgent = headersList.get('user-agent')
const referer = headersList.get('referer')
return (
<div>
<p>User-Agent: {userAgent}</p>
<p>Referer: {referer || 'Direct'}</p>
</div>
)
}
Return Type
Standard Web Headers instance.Methods:
get(name: string): string | null
has(name: string): boolean
getSetCookie(): string[]
entries(): Iterator<[string, string]>
forEach(callback)
Read-only — mutations are ignored.
Usage Notes
Async in Next.js 15+: headers() returns a Promise. Always await it.
Dynamic rendering: Calling headers() opts the page out of static rendering and ISR caching.
Example: Route Handler
// app/api/info/route.ts
import { headers } from 'next/headers'
import { NextResponse } from 'next/server'
export async function GET() {
const headersList = await headers()
const authorization = headersList.get('authorization')
if (!authorization) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
return NextResponse.json({ authenticated: true })
}
cookies
Returns a cookie accessor for reading and writing cookies.
import { cookies } from 'next/headers'
export default async function Page() {
const cookieStore = await cookies()
const theme = cookieStore.get('theme')
return <div>Theme: {theme?.value || 'default'}</div>
}
Return Type
Cookie accessor with read/write methods.Read methods:
get(name: string): { name: string; value: string } | undefined
getAll(): Array<{ name: string; value: string }>
has(name: string): boolean
size: number
Write methods (Route Handlers / Server Actions only):
set(name: string, value: string, options?): this
set(options: { name: string; value: string; ... }): this
delete(name: string): this
Reading Cookies
import { cookies } from 'next/headers'
export default async function ProfilePage() {
const cookieStore = await cookies()
// Get single cookie
const session = cookieStore.get('session')
if (!session) {
redirect('/login')
}
// Get all cookies
const allCookies = cookieStore.getAll()
console.log('All cookies:', allCookies)
// Check existence
const hasTheme = cookieStore.has('theme')
return <Profile sessionId={session.value} />
}
Writing Cookies
Server Actions / Route Handlers only: Writing cookies in Server Components has no effect.
// app/api/login/route.ts
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const { username, password } = await request.json()
const sessionId = await createSession(username, password)
const cookieStore = await cookies()
cookieStore.set('session', sessionId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7 // 1 week
})
return NextResponse.json({ success: true })
}
// Server Action
'use server'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
export async function logout() {
const cookieStore = await cookies()
cookieStore.delete('session')
redirect('/login')
}
Cookie Options
interface CookieOptions {
path?: string // Default: "/"
domain?: string
maxAge?: number // Seconds
expires?: Date
httpOnly?: boolean // Default: false
secure?: boolean // Default: false in dev, true in prod
sameSite?: 'Strict' | 'Lax' | 'None' // Default: 'Lax'
}
draftMode
Enable/disable draft mode for preview deployments.
import { draftMode } from 'next/headers'
export default async function Page() {
const { isEnabled } = await draftMode()
return (
<div>
Draft mode: {isEnabled ? 'ON' : 'OFF'}
</div>
)
}
Return Type
Draft mode controller.interface DraftModeResult {
isEnabled: boolean
enable(): void
disable(): void
}
Usage: Enable Draft Mode
// app/api/draft/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret')
// Verify secret token
if (secret !== process.env.DRAFT_SECRET) {
return new Response('Invalid token', { status: 401 })
}
const draft = await draftMode()
draft.enable()
// Redirect to the page you want to preview
redirect(searchParams.get('slug') || '/')
}
Usage: Disable Draft Mode
// app/api/draft/disable/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
export async function GET() {
const draft = await draftMode()
draft.disable()
redirect('/')
}
Usage: Check Draft Mode
import { draftMode } from 'next/headers'
import { getPost, getDraftPost } from '@/lib/api'
export default async function Post({ params }) {
const { isEnabled } = await draftMode()
const post = isEnabled
? await getDraftPost(params.slug)
: await getPost(params.slug)
return (
<article>
{isEnabled && (
<div className="draft-banner">
You are viewing a draft
</div>
)}
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
)
}
How It Works
Request Context
vinext uses AsyncLocalStorage to maintain per-request context:
- Entry: Server entry sets context before rendering
- Access:
headers() / cookies() read from ALS
- Cleanup: Context is cleared after response
This ensures concurrent requests don’t share state.
Dynamic Usage Tracking
Calling headers() or cookies() marks the page as dynamic:
- ISR caching: Bypassed
- Response headers:
Cache-Control: no-store
- Rendering: On-demand per request
Limitations
Client Components: Cannot call headers() or cookies() in client components. Pass data as props.
Writing in Server Components: Cookie writes in Server Components have no effect. Use Route Handlers or Server Actions.
SSR timing: These functions can only be called after the request context is set. Calling them at module-level or in global scope will throw.
Migration from Pages Router
| Pages Router | App Router |
|---|
req.headers (getServerSideProps) | await headers() (Server Component) |
req.cookies | await cookies() |
res.setHeader('Set-Cookie', ...) | cookieStore.set(...) (Route Handler) |
Source
View source code →
Implementation: /home/daytona/workspace/source/packages/vinext/src/shims/headers.ts