express.static è il middleware integrato che serve i file di una cartella. app.use(express.static(path.join(__dirname, 'public'))) rende ogni file di public disponibile allo stesso path nell'URL: public/style.css risponde a /style.css e public/index.html risponde a /. Express imposta il Content-Type in base all'estensione del file e, quando nessun file corrisponde, passa la richiesta alle tue route invece di rispondere 404.
La risposta a GET /style.css è il testo del file, con Content-Type: text/css; charset=utf-8 nella scheda Headers. Invia GET / per ottenere index.html, poi GET /api/time: quel file non esiste, quindi risponde la route.
Perché il path della cartella ha bisogno di __dirname
express.static('public') risolve 'public' rispetto alla cartella da cui hai avviato Node, non a quella in cui si trova server.js. Funziona quando esegui node server.js dentro il progetto e si rompe quando un process manager o uno script avvia l'app da un'altra posizione:
// project/server.js uses express.static('public')
// cd project && node server.js -> serves project/public
// node project/server.js -> looks for ./public, every file is 404
path.join(__dirname, 'public') è la cartella accanto al file che contiene la riga, ovunque sia stato avviato Node. In un modulo ES usa path.join(import.meta.dirname, 'public') (Node 20.11 e successivi). La pagina sul modulo path spiega nel dettaglio __dirname e path.join.
Servire file sotto un prefisso
Passa prima un path di montaggio per servire la cartella sotto un prefisso dell'URL. Il prefisso non fa parte del path del file sul disco:
GET /assets/app.js restituisce console.log("hi"); come text/javascript, e GET /assets/img/dot.svg arriva nella sottocartella. Invia GET /app.js senza il prefisso: è un 404, perché i file esistono solo sotto /assets. Usa un prefisso per evitare che gli URL dei file si scontrino con le route della tua API.
Header di cache con maxAge
Di default Express invia Cache-Control: public, max-age=0, quindi i browser chiedono conferma al server a ogni uso (e ricevono un rapido 304 Not Modified quando il file non è cambiato, grazie all'header ETag). Per i file il cui nome cambia a ogni build, come app.3f9a1c.js, di' ai browser di tenerli:
Apri la scheda Headers: ora Cache-Control è public, max-age=604800, sette giorni in secondi. index: false disattiva l'invio di index.html per gli URL delle cartelle. Altre opzioni utili sono extensions: ['html'], che permette a /about di trovare about.html, e dotfiles, che di default è 'ignore' così file come .env non vengono mai serviti.
Single page app: tornare a index.html
Un'app React o Vue costruita per il routing lato client ha bisogno che ogni path sconosciuto restituisca index.html, così un reload su /settings non dà 404. Metti il fallback dopo express.static e le route dell'API:
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/api', apiRouter);
// Express 5 wildcard syntax
app.get('/*splat', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});
Errori comuni
Mettere il nome della cartella nell'URL. Con app.use(express.static('public')) l'URL è /style.css, non /public/style.css. Se vuoi /public nell'URL, montala: app.use('/public', express.static(...)).
Collegare i file con path relativi nell'HTML. <link href="style.css"> nella pagina /blog/post chiede /blog/style.css. Inizia il path con una barra: /style.css.
Mettere segreti nella cartella statica. Tutto quello che contiene è pubblico. Tieni fuori file di configurazione, chiavi e codice sorgente.
Domande frequenti
Posso usare express.static più di una volta?
Sì. Registrane uno per cartella; Express le controlla nell'ordine in cui le hai aggiunte e serve il primo file che trova. Dai a ciascuna un path di montaggio diverso quando le cartelle possono contenere file con lo stesso nome.
Express deve servire i file statici in produzione?
Per app piccole va bene. Con molto traffico, metti davanti una CDN o un reverse proxy come Nginx, che serve i file più velocemente e li tiene in cache più vicino agli utenti, e lascia a Express l'API.
Come invio un file specifico da una route?
Usa res.sendFile(path.join(__dirname, 'files', 'report.pdf')). Il path deve essere assoluto (oppure passi l'opzione root). Usa invece res.download() quando il browser deve salvare il file invece di mostrarlo.