Skip to content

response

The response package provides helper functions for creating common HTTP responses. Each helper handles the correct headers, status codes, and body encoding so you do not have to set them manually.

Installation

The response package is included with Remix. No additional installation is needed.

Import

The main export bundles all helpers:

ts
import {
  createHtmlResponse,
  createFileResponse,
  createRedirectResponse,
  compressResponse,
} from 'remix/response'

Each helper is also available from its own sub-export for tree-shaking:

ts
import { createHtmlResponse } from 'remix/response/html'
import { createFileResponse } from 'remix/response/file'
import { createRedirectResponse } from 'remix/response/redirect'
import { compressResponse } from 'remix/response/compress'

API

createHtmlResponse(body, init?)

Creates a Response with Content-Type: text/html; charset=utf-8 and a <!DOCTYPE html> prefix.

ts
function createHtmlResponse(
  body: string,
  init?: ResponseInit,
): Response
ts
let response = createHtmlResponse('<html><body>Hello</body></html>')

// Status is 200, Content-Type is text/html; charset=utf-8
// Body is prefixed with <!DOCTYPE html>

Pass init to set a custom status or additional headers:

ts
let response = createHtmlResponse('<html><body>Not Found</body></html>', {
  status: 404,
  headers: {
    'Cache-Control': 'no-store',
  },
})

createFileResponse(file, request, options?)

Creates a Response that streams a File or LazyFile to the client with proper headers for ETag-based caching, conditional requests, and byte-range requests.

ts
function createFileResponse(
  file: File,
  request: Request,
  options?: FileResponseOptions,
): Response
ts
import { openLazyFile } from 'remix/fs'

let file = await openLazyFile('./uploads/photo.jpg')
let response = createFileResponse(file, request)

The function automatically handles:

  • Content-Type -- Set from the file's type property.
  • Content-Length -- Set from the file's size property.
  • ETag -- Generated from the file's size and last modified time.
  • Last-Modified -- Set from the file's lastModified property.
  • If-None-Match / If-Modified-Since -- Returns 304 Not Modified when appropriate.
  • Range -- Returns 206 Partial Content for byte-range requests.
  • Cache-Control -- Set from options.cacheControl if provided.

Options

OptionTypeDefaultDescription
cacheControlstring--The Cache-Control header value.
etagstringAuto-generatedA custom ETag value.

createRedirectResponse(url, init?)

Creates a redirect Response.

ts
function createRedirectResponse(
  url: string,
  init?: number | ResponseInit,
): Response

The second argument can be a status code or a full ResponseInit:

ts
// 302 Found (default)
createRedirectResponse('/dashboard')

// 301 Moved Permanently
createRedirectResponse('/new-url', 301)

// Custom headers
createRedirectResponse('/dashboard', {
  status: 303,
  headers: {
    'Set-Cookie': 'session=abc; Path=/',
  },
})

compressResponse(response, encoding)

Compresses a response body using the specified encoding.

ts
function compressResponse(
  response: Response,
  encoding: 'gzip' | 'br' | 'deflate',
): Response
ts
let response = new Response('Hello, world!')
let compressed = compressResponse(response, 'gzip')

compressed.headers.get('Content-Encoding') // 'gzip'

The function:

  • Compresses the response body using the specified algorithm.
  • Sets the Content-Encoding header.
  • Removes the Content-Length header (since the compressed size is unknown until streaming completes).
  • Returns the original response unchanged if the body is empty.

Examples

HTML Page

ts
import { createHtmlResponse } from 'remix/response/html'

router.map(homeRoute, () => {
  return createHtmlResponse(`
    <html>
      <head><title>Home</title></head>
      <body><h1>Welcome</h1></body>
    </html>
  `)
})

File Download

ts
import { openLazyFile } from 'remix/fs'
import { createFileResponse } from 'remix/response/file'

router.map(downloadRoute, async ({ request, params }) => {
  let file = await openLazyFile(`./files/${params.name}`)
  return createFileResponse(file, request, {
    cacheControl: 'public, max-age=31536000, immutable',
  })
})

Post/Redirect/Get Pattern

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

router.map(createPostRoute, async ({ request }) => {
  let formData = await request.formData()
  let title = formData.get('title') as string

  await createPost(title)

  // Redirect to the new post after creation
  return createRedirectResponse('/posts', 303)
})

Compressed API Response

ts
import { compressResponse } from 'remix/response/compress'
import { AcceptEncoding } from 'remix/headers'

router.map(apiRoute, async ({ request }) => {
  let data = await getLargeDataset()
  let response = Response.json(data)

  let ae = AcceptEncoding.parse(
    request.headers.get('Accept-Encoding') ?? '',
  )

  let encoding = ae.negotiate(['br', 'gzip'])

  if (encoding) {
    return compressResponse(response, encoding)
  }

  return response
})
  • headers --- Typed header parsing and serialization.
  • fs --- Read files from disk for serving.
  • lazy-file --- Deferred file reading for efficient serving.
  • compression-middleware --- Automatic response compression as middleware.

Released under the MIT License.