Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt

Use this file to discover all available pages before exploring further.

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 Response API, works in any JavaScript runtime
  • 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 remix

Usage

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-run/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), 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')
  },
})

Conditional Requests

createFileResponse automatically handles conditional requests:
import { createFileResponse } from 'remix/response/file'
import { openLazyFile } from 'remix/fs'

async function handler(request: Request) {
  let file = openLazyFile('./public/document.pdf')
  
  // Automatically handles If-None-Match, If-Modified-Since, etc.
  let response = await createFileResponse(file, request, {
    cacheControl: 'public, max-age=86400',
  })
  
  // Response may be 304 Not Modified if conditions match
  return response
}

Range Requests

Support partial content delivery for large files:
import { createFileResponse } from 'remix/response/file'
import { openLazyFile } from 'remix/fs'

async function handler(request: Request) {
  let file = openLazyFile('./public/video.mp4')
  
  // Automatically handles Range header
  let response = await createFileResponse(file, request, {
    acceptRanges: true,
  })
  
  // Response may be 206 Partial Content if Range header is present
  return response
}

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-run/html-template, Blobs/Files, ArrayBuffers, and ReadableStreams.

With SafeHtml

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 HTML

Complete Pages

import { html } from 'remix/html-template'
import { createHtmlResponse } from 'remix/response/html'

async function handler(request: Request) {
  let user = await getUser(request)
  
  return createHtmlResponse(html`
    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>Dashboard</title>
      </head>
      <body>
        <h1>Welcome, ${user.name}!</h1>
      </body>
    </html>
  `)
}

Redirect Responses

The createRedirectResponse helper creates redirect responses. The main improvements over the native Response.redirect API are:
  • Accepts a relative location instead of a full URL
  • Accepts a ResponseInit object 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' },
})

Common Redirect Patterns

import { createRedirectResponse } from 'remix/response/redirect'

// After form submission (303 See Other)
router.post('/submit', async (context) => {
  await processForm(context)
  return createRedirectResponse('/success', 303)
})

// Permanent redirect (301 Moved Permanently)
router.get('/old-page', () => {
  return createRedirectResponse('/new-page', 301)
})

// Temporary redirect (302 Found - default)
router.get('/temp', () => {
  return createRedirectResponse('/temporary-location')
})

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-Encoding header
  • Responses that are already compressed (existing Content-Encoding)
  • Responses with Cache-Control: no-transform
  • Responses with Content-Length below threshold (default: 1024 bytes)
  • Responses with range support (Accept-Ranges: bytes)
  • 206 Partial Content responses
  • HEAD requests (only headers are modified)

Options

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.
  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.
  brotli: {
    params: {
      [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
    },
  },
})

Usage with Middleware

import { compressResponse } from 'remix/response/compress'
import { createRouter } from 'remix/fetch-router'

let router = createRouter()

// Add compression middleware
router.use(async (context, next) => {
  let response = await next()
  return compressResponse(response, context.request)
})

router.get('/api/data', () => {
  return Response.json({ large: 'data object' })
})

API Reference

createFileResponse(file, request, options?)

Create a response for serving files with full HTTP semantics. Parameters:
  • file: File - The file to serve
  • request: Request - The incoming request
  • options?: FileResponseOptions - Configuration options
Returns: Promise<Response>

createHtmlResponse(html, init?)

Create an HTML response with proper Content-Type. Parameters:
  • html: string | SafeHtml | Blob | ArrayBuffer | ReadableStream - The HTML content
  • init?: ResponseInit - Additional response options
Returns: Response

createRedirectResponse(location, init?)

Create a redirect response. Parameters:
  • location: string - The redirect location (can be relative)
  • init?: number | ResponseInit - Status code or response options (default: 302)
Returns: Response

compressResponse(response, request, options?)

Compress a response based on Accept-Encoding. Parameters:
  • response: Response - The response to compress
  • request: Request - The incoming request
  • options?: CompressOptions - Compression configuration
Returns: Promise<Response>