Headers

Catmint provides a headers() async function for reading the HTTP headers of the current request. It returns a read-only Headers object (Web API) and is available in server components, middleware, and server functions.

Basic Usage

import { headers } from 'catmint/headers'

const requestHeaders = await headers()

const contentType = requestHeaders.get('content-type')
const authorization = requestHeaders.get('authorization')
const userAgent = requestHeaders.get('user-agent')

The headers() function is asynchronous and must be awaited. It reads from the current request context provided by AsyncLocalStorage, so you do not need to pass the request object manually.

Server-Only Enforcement

The headers() function is server-only. Catmint's Vite plugin blocks any import of catmint/headers in client-side code, producing a build error:

// app/components/example.client.tsx
import { headers } from 'catmint/headers'

// Build error: "catmint/headers" cannot be imported in client code.
// Headers are only available on the server.

If you need header values on the client, read them in a server component or server function and pass the values down as props or return them as data.

ReadonlyHeaders

The object returned by headers() is a ReadonlyHeaders wrapper around the standard Web API Headers class. It exposes all read methods but does not allow mutation. Attempting to call set(), append(), or delete() will throw a TypeError.

Available read methods:

MethodReturn TypeDescription
get(name)string | nullReturns the value of the header, or null
has(name)booleanWhether the header exists
entries()IteratorIterates over all header key-value pairs
keys()IteratorIterates over all header names
values()IteratorIterates over all header values
forEach(cb)voidCalls the callback for each header entry

Usage in Server Components

Read headers in a server component to make decisions about rendering. Since server components are async, you can await headers() directly:

// app/page.tsx
import { headers } from 'catmint/headers'

export default async function HomePage() {
  const h = await headers()
  const locale = h.get('accept-language')?.split(',')[0] || 'en'

  return (
    <div>
      <p>Detected locale: {locale}</p>
    </div>
  )
}

Usage in Middleware

In middleware, you already have access to the Request object directly. However, headers() can still be useful in shared utility functions called from middleware:

// app/lib/geo.server.ts
import { headers } from 'catmint/headers'

export async function getClientCountry(): Promise<string> {
  const h = await headers()
  return h.get('x-vercel-ip-country') || h.get('cf-ipcountry') || 'US'
}

export async function getClientIp(): Promise<string> {
  const h = await headers()
  return (
    h.get('x-forwarded-for')?.split(',')[0]?.trim() ||
    h.get('x-real-ip') ||
    '0.0.0.0'
  )
}
// app/middleware.ts
import { getClientCountry } from './lib/geo.server'
import { statusResponse } from 'catmint/server'

export async function middleware(request: Request, next: () => Promise<Response>) {
  const country = await getClientCountry()

  if (country === 'XX') {
    return statusResponse(403, 'Access restricted in your region.')
  }

  return next()
}

Usage in Server Functions

Server functions run in the context of a request, so headers() works as expected:

// app/lib/analytics.fn.ts
import { createServerFn } from 'catmint/server'
import { headers } from 'catmint/headers'

export const trackEvent = createServerFn(
  async (event: { name: string; data: Record<string, string> }) => {
    const h = await headers()
    const userAgent = h.get('user-agent') || 'unknown'
    const referer = h.get('referer') || 'direct'

    await analyticsService.track({
      ...event,
      userAgent,
      referer,
      timestamp: Date.now(),
    })
  }
)

Common Patterns

Content negotiation

// app/api/data/endpoint.ts
import { headers } from 'catmint/headers'

export async function GET() {
  const h = await headers()
  const accept = h.get('accept') || ''
  const data = await fetchData()

  if (accept.includes('application/xml')) {
    return new Response(toXml(data), {
      headers: { 'content-type': 'application/xml' },
    })
  }

  return Response.json(data)
}

Authentication

// app/lib/auth.server.ts
import { headers } from 'catmint/headers'

export async function verifyAuth() {
  const h = await headers()
  const token = h.get('authorization')?.replace('Bearer ', '')

  if (!token) {
    return null
  }

  return await verifyJwt(token)
}

CORS and forwarded headers

import { headers } from 'catmint/headers'

export async function getRequestOrigin(): Promise<string> {
  const h = await headers()
  const proto = h.get('x-forwarded-proto') || 'https'
  const host = h.get('x-forwarded-host') || h.get('host') || 'localhost'
  return `${proto}://${host}`
}

Summary

APIDetails
headers()Async function returning ReadonlyHeaders
Importcatmint/headers
EnvironmentServer only (blocked in client code by Vite plugin)
ContextUses AsyncLocalStorage -- no need to pass the Request

Next: Cookies →