Serving Static Files
1. Using express.static() Middleware
Form Effect
express.static("public")Serve at root
app.use("/static", express.static("public"))Mount under prefix
express.static("public", opts)Pass serve-static options
2. Setting Static Directory
Example: Absolute path
import path from "node:path" ;
import { fileURLToPath } from "node:url" ;
const __dirname = path. dirname ( fileURLToPath ( import . meta .url));
app. use (express. static (path. join (__dirname, "public" )));
Warning: Always use absolute paths — relative paths resolve from the CWD where Node was launched, breaking PM2 / systemd setups.
3. Serving Multiple Static Directories
Example: Order matters (first found wins)
app. use (express. static ( "dist" )); // Built assets
app. use (express. static ( "public" )); // Untouched static
4. Setting Virtual Path Prefix
Example: Mount under /assets
app. use ( "/assets" , express. static ( "public" ));
// File public/logo.png → GET /assets/logo.png
Benefit Detail
Decoupling URL doesn't expose folder name
Versioning /assets/v2/...
CDN aliasing Match CDN path
Asset Type maxAge immutable
Hashed bundles (app.[hash].js) 1ytrue
Images 30dfalse
index.html0false (no-cache)
Example: Long-cache hashed assets
app. use ( "/static" , express. static ( "dist" , {
maxAge: "1y" ,
immutable: true ,
setHeaders : ( res , filePath ) => {
if (filePath. endsWith ( ".html" )) res. setHeader ( "Cache-Control" , "no-cache" );
}
}));
6. Setting Index Files
Setting Effect
index: "index.html"Default
index: ["index.html", "index.htm"]Try in order
index: falseDisable index serving
7. Enabling Directory Listing
Example: serve-index for browsing
import serveIndex from "serve-index" ;
app. use ( "/files" , express. static ( "uploads" ), serveIndex ( "uploads" , { icons: true }));
Warning: Never enable directory listing in production for sensitive folders — exposes filenames.
Option Default Purpose
etagtrueGenerate weak ETag from inode/size/mtime
lastModifiedtrueSend Last-Modified header
Client request 1: GET /logo.png
Server response: 200 OK + ETag: "abc123"
Client request 2: GET /logo.png + If-None-Match: "abc123"
Server response: 304 Not Modified ← no body, saves bandwidth
9. Handling Fallback Files
Example: SPA fallback to index.html
app. use (express. static ( "dist" ));
// Catch-all: serve SPA for unknown paths (Express 5)
app. get ( "/{*splat}" , ( req , res ) => {
res. sendFile (path. join (__dirname, "dist/index.html" ));
});
Use Case Pattern
SPA Catch-all → index.html
Multi-tenant Per-tenant static dir
10. Serving Hidden Files
dotfiles Behavior
"ignore" (default)404 + skip dotfiles
"deny"403 Forbidden
"allow"Serve normally
Example: Allow .well-known for ACME
app. use (express. static ( "public" , { dotfiles: "allow" }));
// Or scope it to ACME challenge only
app. use ( "/.well-known" , express. static ( ".well-known" , { dotfiles: "allow" }));