Skip to main content

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'

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

headers
Promise<Headers>
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

cookies
Promise<RequestCookies>
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')
}
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

draftMode
Promise<DraftModeResult>
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:
  1. Entry: Server entry sets context before rendering
  2. Access: headers() / cookies() read from ALS
  3. 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 RouterApp Router
req.headers (getServerSideProps)await headers() (Server Component)
req.cookiesawait cookies()
res.setHeader('Set-Cookie', ...)cookieStore.set(...) (Route Handler)

Source

View source code → Implementation: /home/daytona/workspace/source/packages/vinext/src/shims/headers.ts