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:
| Method | Return Type | Description |
|---|---|---|
get(name) | string | null | Returns the value of the header, or null |
has(name) | boolean | Whether the header exists |
entries() | Iterator | Iterates over all header key-value pairs |
keys() | Iterator | Iterates over all header names |
values() | Iterator | Iterates over all header values |
forEach(cb) | void | Calls 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
| API | Details |
|---|---|
headers() | Async function returning ReadonlyHeaders |
| Import | catmint/headers |
| Environment | Server only (blocked in client code by Vite plugin) |
| Context | Uses AsyncLocalStorage -- no need to pass the Request |
