response
Response helper utilities for the web Fetch API. response provides focused helpers for common HTTP responses with correct headers and caching semantics.
Features
- Web Standards Compliant: Built on the standard
ResponseAPI, works in any JavaScript runtime (Node.js, Bun, Deno, Cloudflare Workers) - File Responses: Full HTTP semantics including ETags, Last-Modified, conditional requests, and Range support
- HTML Responses: Automatic DOCTYPE prepending and proper Content-Type headers
- Redirect Responses: Simple redirect creation with customizable status codes
- Compress Responses: Streaming compression based on Accept-Encoding header
Installation
npm i remixUsage
This package provides no default export. Instead, import the specific helper you need:
import { createFileResponse } from 'remix/response/file'
import { createHtmlResponse } from 'remix/response/html'
import { createRedirectResponse } from 'remix/response/redirect'
import { compressResponse } from 'remix/response/compress'File Responses
The createFileResponse helper creates a response for serving files with full HTTP semantics. It works with both native File objects and LazyFile from remix/lazy-file:
import { createFileResponse } from 'remix/response/file'
import { openLazyFile } from 'remix/fs'
let lazyFile = openLazyFile('./public/image.jpg')
let response = await createFileResponse(lazyFile, request, {
cacheControl: 'public, max-age=3600',
})Features
- Content-Type and Content-Length headers
- ETag generation (weak or strong)
- Last-Modified headers
- Cache-Control headers
- Conditional requests (
If-None-Match,If-Modified-Since,If-Match,If-Unmodified-Since) - Range requests for partial content (
206 Partial Content) - HEAD request support
Options
await createFileResponse(file, request, {
// Cache-Control header value.
// Defaults to `undefined` (no Cache-Control header).
cacheControl: 'public, max-age=3600',
// ETag generation strategy:
// - 'weak': Generates weak ETags based on file size and mtime (default)
// - 'strong': Generates strong ETags by hashing file content
// - false: Disables ETag generation
etag: 'weak',
// Hash algorithm for strong ETags (Web Crypto API algorithm names).
// Only used when etag: 'strong'.
// Defaults to 'SHA-256'.
digest: 'SHA-256',
// Whether to generate Last-Modified headers.
// Defaults to `true`.
lastModified: true,
// Whether to support HTTP Range requests for partial content.
// Defaults to `true`.
acceptRanges: true,
})Strong ETags and Content Hashing
For assets that require strong validation (e.g., to support If-Match preconditions or If-Range with Range requests), configure strong ETag generation:
return createFileResponse(file, request, {
etag: 'strong',
})By default, strong ETags are generated using the Web Crypto API with the 'SHA-256' algorithm. You can customize this:
return createFileResponse(file, request, {
etag: 'strong',
// Specify a different hash algorithm
digest: 'SHA-512',
})For large files or custom hashing requirements, provide a custom digest function:
await createFileResponse(file, request, {
etag: 'strong',
async digest(file) {
// Custom streaming hash for large files
let { createHash } = await import('node:crypto')
let hash = createHash('sha256')
for await (let chunk of file.stream()) {
hash.update(chunk)
}
return hash.digest('hex')
},
})HTML Responses
The createHtmlResponse helper creates HTML responses with proper Content-Type and DOCTYPE handling:
import { createHtmlResponse } from 'remix/response/html'
let response = createHtmlResponse('<h1>Hello, World!</h1>')
// Content-Type: text/html; charset=UTF-8
// Body: <!DOCTYPE html><h1>Hello, World!</h1>The helper automatically prepends <!DOCTYPE html> if not already present. It works with strings, SafeHtml from remix/html-template, Blobs/Files, ArrayBuffers, and ReadableStreams.
import { html } from 'remix/html-template'
import { createHtmlResponse } from 'remix/response/html'
let name = '<script>alert(1)</script>'
let response = createHtmlResponse(html`<h1>Hello, ${name}!</h1>`)
// Safely escaped HTMLRedirect Responses
The createRedirectResponse helper creates redirect responses. The main improvements over the native Response.redirect API are:
- Accepts a relative
locationinstead of a full URL. This isn't technically spec-compliant, but it's so widespread that many applications use relative redirects regularly without issues. - Accepts a
ResponseInitobject as the second argument, allowing you to set additional headers and status code.
import { createRedirectResponse } from 'remix/response/redirect'
// Default 302 redirect
let response = createRedirectResponse('/login')
// Custom status code
let response = createRedirectResponse('/new-page', 301)
// With additional headers
let response = createRedirectResponse('/dashboard', {
status: 303,
headers: { 'X-Redirect-Reason': 'authentication' },
})Compress Responses
The compressResponse helper compresses a Response based on the client's Accept-Encoding header:
import { compressResponse } from 'remix/response/compress'
let response = new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
})
let compressed = await compressResponse(response, request)Compression is automatically skipped for:
- Responses with no
Accept-Encodingheader - Responses that are already compressed (existing
Content-Encoding) - Responses with
Cache-Control: no-transform - Responses with
Content-Lengthbelow threshold (default: 1024 bytes) - Responses with range support (
Accept-Ranges: bytes) - 206 Partial Content responses
- HEAD requests (only headers are modified)
Options
The compressResponse helper accepts options to customize compression behavior:
await compressResponse(response, request, {
// Minimum size in bytes to compress (only enforced if Content-Length is present).
// Default: 1024
threshold: 1024,
// Which encodings the server supports for negotiation.
// Defaults to ['br', 'gzip', 'deflate']
encodings: ['br', 'gzip', 'deflate'],
// node:zlib options for gzip/deflate compression.
// For SSE responses (text/event-stream), flush: Z_SYNC_FLUSH
// is automatically applied unless you explicitly set a flush value.
// See: https://nodejs.org/api/zlib.html#class-options
zlib: {
level: 6,
},
// node:zlib options for Brotli compression.
// For SSE responses (text/event-stream), flush: BROTLI_OPERATION_FLUSH
// is automatically applied unless you explicitly set a flush value.
// See: https://nodejs.org/api/zlib.html#class-brotlioptions
brotli: {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 4,
},
},
})Range Requests and Compression
Range requests and compression are mutually exclusive. When Accept-Ranges: bytes is present in the response headers, compressResponse will not compress the response. This is why the createFileResponse helper enables ranges only for non-compressible MIME types by default - to allow text-based assets to be compressed while still supporting resumable downloads for media files.
Related Packages
remix/headers- Type-safe HTTP header manipulationremix/html-template- Safe HTML templating with automatic escapingremix/fs- File system utilities includingopenFileremix/fetch-router- Build HTTP routers using the web fetch APIremix/mime- MIME type utilities
License
See LICENSE