Skip to main content

Static Middleware

Static file serving middleware that serves files from a directory with support for ETags, range requests, and conditional requests.

Installation

npm i remix

Function

staticFiles()

Creates middleware that serves static files from the filesystem. Signature:
function staticFiles(root: string, options?: StaticFilesOptions): Middleware
Parameters:
  • root - The root directory to serve files from (absolute or relative to cwd)
  • options - Optional configuration for file responses
Returns: The static files middleware. Note: Uses the URL pathname to resolve files, removing the leading slash to make it a relative path. The middleware always falls through to the handler if the file is not found or an error occurs.

Options

StaticFilesOptions

interface StaticFilesOptions extends FileResponseOptions {
  filter?: (path: string) => boolean
  acceptRanges?: boolean | AcceptRangesFunction
  index?: boolean | string[]
  listFiles?: boolean
}

filter

Filter function to determine which files should be served.
  • Type: (path: string) => boolean
  • Default: undefined (all files)

acceptRanges

Whether to support HTTP Range requests for partial content.
  • Type: boolean | AcceptRangesFunction
  • Default: Enables ranges only for non-compressible MIME types
Note: Range requests and compression are mutually exclusive. When Accept-Ranges: bytes is present, the compression middleware will not compress the response.

index

Files to try and serve as the index file when the request path targets a directory.
  • Type: boolean | string[]
  • Default: true (uses ['index.html', 'index.htm'])
  • Values:
    • true - Use default index files
    • false - Disable index file serving
    • string[] - Custom list of index files to try in order

listFiles

Whether to return an HTML page listing the files in a directory when the request path targets a directory.
  • Type: boolean
  • Default: false
Note: If both listFiles and index are set, index takes precedence.

cacheControl

Cache-Control header value for responses.
  • Type: string
  • Default: undefined

etag

Whether to generate ETags for responses.
  • Type: boolean | 'strong' | 'weak'
  • Default: 'weak'

Basic Usage

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [staticFiles('./public')],
})

router.get('/', () => new Response('Home'))

With Cache Control

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    staticFiles('./public', {
      cacheControl: 'public, max-age=31536000, immutable', // 1 year
    }),
  ],
})

Filter Files

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    staticFiles('./public', {
      filter(path) {
        // Don't serve hidden files
        return !path.startsWith('.')
      },
    }),
  ],
})

Multiple Directories

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    staticFiles('./public'),
    staticFiles('./assets', {
      cacheControl: 'public, max-age=31536000',
    }),
  ],
})

Custom Index Files

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    staticFiles('./public', {
      index: ['index.html', 'default.html', 'home.html'],
    }),
  ],
})

Disable Index Files

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    staticFiles('./public', {
      index: false, // Don't serve index files
    }),
  ],
})

Directory Listing

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    staticFiles('./public', {
      listFiles: true, // Show directory contents when no index file
    }),
  ],
})

Range Requests

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

// Force range request support for all files
let router = createRouter({
  middleware: [
    staticFiles('./public', {
      acceptRanges: true,
    }),
  ],
})

// Enable ranges for videos only
let router2 = createRouter({
  middleware: [
    staticFiles('./public', {
      acceptRanges: (file) => file.type.startsWith('video/'),
    }),
  ],
})

Strong ETags

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    staticFiles('./public', {
      etag: 'strong', // Use strong ETags instead of weak
    }),
  ],
})

Disable ETags

import { createRouter } from 'remix/fetch-router'
import { staticFiles } from 'remix/static-middleware'

let router = createRouter({
  middleware: [
    staticFiles('./public', {
      etag: false, // Disable ETag generation
    }),
  ],
})

Security

  • Prevents path traversal attacks (e.g., ../../../etc/passwd)
  • Only serves files with GET and HEAD requests
  • Respects the configured root directory boundary

Features

  • ETag support (weak and strong)
  • Range requests support (HTTP 206 Partial Content)
  • Conditional request support (If-None-Match, If-Modified-Since)
  • Path traversal protection
  • Automatic fallback to next middleware/handler if file not found
  • fetch-router - Router for the web Fetch API
  • fs - File system utilities
  • lazy-file - Used internally for streaming file contents

Build docs developers (and LLMs) love