headers
The headers package provides typed classes for parsing and serializing common HTTP headers. Each class offers a structured API for reading and writing header values instead of manual string manipulation.
Installation
The headers package is included with Remix. No additional installation is needed.
Import
import {
Accept,
AcceptEncoding,
AcceptLanguage,
CacheControl,
ContentDisposition,
ContentRange,
ContentType,
Cookie,
SetCookie,
IfMatch,
IfNoneMatch,
IfRange,
Range,
Vary,
} from 'remix/headers'Common Pattern
Every header class follows the same pattern:
parse(value)-- Static method that parses a header string into a typed object.stringify()-- Serializes the object back to a header string.- Constructor -- Creates a new instance from typed values.
let cache = CacheControl.parse('public, max-age=3600')
console.log(cache.maxAge) // 3600
console.log(cache.stringify()) // 'public, max-age=3600'Content Negotiation
Accept
Parses and works with the Accept request header for content type negotiation.
let accept = Accept.parse(request.headers.get('Accept')!)
// Check if the client accepts JSON
accept.accepts('application/json') // true
// Get all accepted types, ordered by preference
accept.types // [{ type: 'text/html', quality: 1 }, { type: 'application/json', quality: 0.9 }]
// Negotiate the best type from a list
accept.negotiate(['text/html', 'application/json']) // 'text/html'AcceptEncoding
Parses the Accept-Encoding request header for compression negotiation.
let encoding = AcceptEncoding.parse(request.headers.get('Accept-Encoding')!)
encoding.accepts('br') // true
encoding.accepts('gzip') // true
// Get preferred encoding from a list of supported encodings
encoding.negotiate(['br', 'gzip', 'deflate']) // 'br'AcceptLanguage
Parses the Accept-Language request header for language negotiation.
let lang = AcceptLanguage.parse(request.headers.get('Accept-Language')!)
lang.accepts('en-US') // true
// Negotiate the best language from available translations
lang.negotiate(['en', 'fr', 'de', 'ja']) // 'en'
lang.languages // [{ language: 'en-US', quality: 1 }, { language: 'fr', quality: 0.8 }]Caching
CacheControl
Parses and builds Cache-Control headers with typed directives.
let cache = CacheControl.parse('public, max-age=3600, s-maxage=86400')
cache.public // true
cache.maxAge // 3600
cache.sMaxAge // 86400
cache.noCache // false
cache.noStore // false
cache.stringify() // 'public, max-age=3600, s-maxage=86400'Create a new Cache-Control header:
let cache = new CacheControl({
public: true,
maxAge: 3600,
staleWhileRevalidate: 60,
})
cache.stringify() // 'public, max-age=3600, stale-while-revalidate=60'| Property | Type | Description |
|---|---|---|
public | boolean | Response can be stored by any cache. |
private | boolean | Response is for a single user only. |
noCache | boolean | Cache must revalidate before use. |
noStore | boolean | Response must not be cached. |
maxAge | number | Time in seconds the response is fresh. |
sMaxAge | number | Time in seconds for shared caches. |
mustRevalidate | boolean | Cache must not use stale responses. |
staleWhileRevalidate | number | Seconds a stale response can be used while revalidating. |
staleIfError | number | Seconds a stale response can be used if revalidation fails. |
immutable | boolean | Response will not change during its freshness lifetime. |
Content Headers
ContentType
Parses and builds Content-Type headers.
let ct = ContentType.parse('text/html; charset=utf-8')
ct.type // 'text/html'
ct.charset // 'utf-8'
ct.stringify() // 'text/html; charset=utf-8'let ct = new ContentType('application/json', { charset: 'utf-8' })
ct.stringify() // 'application/json; charset=utf-8'ContentDisposition
Parses and builds Content-Disposition headers for file downloads and inline display.
let cd = ContentDisposition.parse('attachment; filename="report.pdf"')
cd.type // 'attachment'
cd.filename // 'report.pdf'
cd.stringify() // 'attachment; filename="report.pdf"'let cd = new ContentDisposition('attachment', { filename: 'data.csv' })
cd.stringify() // 'attachment; filename="data.csv"'ContentRange
Parses and builds Content-Range response headers for byte-range responses.
let cr = ContentRange.parse('bytes 0-1023/4096')
cr.unit // 'bytes'
cr.start // 0
cr.end // 1023
cr.size // 4096
cr.stringify() // 'bytes 0-1023/4096'Cookies
Cookie
Parses the Cookie request header into a key/value map.
let cookies = Cookie.parse(request.headers.get('Cookie')!)
cookies.get('session') // 'abc123'
cookies.get('theme') // 'dark'
cookies.has('session') // trueSetCookie
Builds Set-Cookie response headers with all standard attributes.
let cookie = new SetCookie('session', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 86400,
path: '/',
})
cookie.stringify()
// 'session=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=86400; Path=/'Parse an existing Set-Cookie header:
let cookie = SetCookie.parse('session=abc123; HttpOnly; Secure; Path=/')
cookie.name // 'session'
cookie.value // 'abc123'
cookie.httpOnly // true
cookie.secure // true
cookie.path // '/'Conditional Requests
IfMatch
Parses the If-Match request header for conditional requests based on ETags.
let im = IfMatch.parse(request.headers.get('If-Match')!)
im.matches('"abc123"') // true
im.any // false (true when header is '*')
im.etags // ['"abc123"', '"def456"']IfNoneMatch
Parses the If-None-Match request header.
let inm = IfNoneMatch.parse(request.headers.get('If-None-Match')!)
inm.matches('"abc123"') // true
inm.any // falseIfRange
Parses the If-Range request header, which can be either an ETag or a date.
let ir = IfRange.parse(request.headers.get('If-Range')!)
ir.etag // '"abc123"' or undefined
ir.date // Date object or undefinedRange
Parses the Range request header for byte-range requests.
let range = Range.parse(request.headers.get('Range')!)
range.unit // 'bytes'
range.ranges // [{ start: 0, end: 1023 }, { start: 2048, end: 4095 }]Other Headers
Vary
Parses and builds the Vary response header.
let vary = Vary.parse(response.headers.get('Vary')!)
vary.headers // ['Accept', 'Accept-Encoding']
vary.has('Accept') // true
vary.add('Accept-Language')
vary.stringify() // 'Accept, Accept-Encoding, Accept-Language'Examples
Content Negotiation
import { Accept, AcceptEncoding } from 'remix/headers'
router.map(apiRoute, async ({ request }) => {
let accept = Accept.parse(request.headers.get('Accept') ?? '*/*')
if (accept.accepts('application/json')) {
return Response.json({ message: 'Hello' })
}
return new Response('Hello', {
headers: { 'Content-Type': 'text/plain' },
})
})Cache Headers
import { CacheControl } from 'remix/headers'
let cache = new CacheControl({
public: true,
maxAge: 60,
staleWhileRevalidate: 300,
})
return new Response(body, {
headers: {
'Cache-Control': cache.stringify(),
},
})File Download
import { ContentDisposition } from 'remix/headers'
let cd = new ContentDisposition('attachment', {
filename: 'report-2024.pdf',
})
return new Response(fileStream, {
headers: {
'Content-Disposition': cd.stringify(),
'Content-Type': 'application/pdf',
},
})Set a Cookie
import { SetCookie } from 'remix/headers'
let cookie = new SetCookie('theme', 'dark', {
maxAge: 365 * 24 * 60 * 60,
path: '/',
sameSite: 'lax',
})
return new Response('OK', {
headers: {
'Set-Cookie': cookie.stringify(),
},
})Related
- cookie --- Higher-level cookie management.
- response --- Response helpers that set common headers automatically.
- Middleware --- Use headers in middleware for cross-cutting concerns.