express.static to wbudowany middleware, który serwuje pliki z folderu. app.use(express.static(path.join(__dirname, 'public'))) udostępnia każdy plik z public pod tą samą ścieżką w URL: public/style.css odpowiada na /style.css, a public/index.html na /. Express ustawia Content-Type na podstawie rozszerzenia pliku, a gdy żaden plik nie pasuje, przekazuje żądanie dalej do twoich tras zamiast odpowiadać 404.
Odpowiedź na GET /style.css to treść pliku, z Content-Type: text/css; charset=utf-8 w zakładce Headers. Wyślij GET /, żeby dostać index.html, a potem GET /api/time: takiego pliku nie ma, więc odpowiada trasa.
Dlaczego ścieżka folderu wymaga __dirname
express.static('public') rozwiązuje 'public' względem folderu, z którego uruchomiłeś Node, a nie folderu, w którym leży server.js. Działa, gdy uruchamiasz node server.js w projekcie, i psuje się, gdy menedżer procesów albo skrypt uruchamia aplikację z innego miejsca:
// 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') to folder obok pliku, w którym jest ta linia, niezależnie od tego, gdzie uruchomiono Node. W module ES użyj path.join(import.meta.dirname, 'public') (od Node 20.11). Strona o module path dokładnie opisuje __dirname i path.join.
Pliki pod prefiksem
Przekaż najpierw ścieżkę podpięcia, żeby serwować folder pod prefiksem URL. Prefiks nie jest częścią ścieżki pliku na dysku:
GET /assets/app.js zwraca console.log("hi"); jako text/javascript, a GET /assets/img/dot.svg sięga do podfolderu. Wyślij GET /app.js bez prefiksu: to 404, bo pliki istnieją tylko pod /assets. Prefiks sprawia, że URL-e plików nie kolidują z trasami twojego API.
Nagłówki cache z maxAge
Domyślnie Express wysyła Cache-Control: public, max-age=0, więc przeglądarki przy każdym użyciu pytają serwer (i dostają szybkie 304 Not Modified, gdy plik się nie zmienił, dzięki nagłówkowi ETag). Przy plikach, których nazwy zmieniają się przy każdym buildzie, np. app.3f9a1c.js, powiedz przeglądarkom, żeby je trzymały:
Otwórz zakładkę Headers: Cache-Control to teraz public, max-age=604800, czyli siedem dni w sekundach. index: false wyłącza serwowanie index.html dla URL-i folderów. Inne przydatne opcje to extensions: ['html'], dzięki której /about znajdzie about.html, oraz dotfiles, domyślnie 'ignore', więc pliki takie jak .env nigdy nie są serwowane.
Aplikacje SPA: powrót do index.html
Aplikacja React albo Vue z routingiem po stronie klienta potrzebuje index.html dla każdej nieznanej ścieżki, żeby odświeżenie na /settings nie dawało 404. Umieść fallback po express.static i trasach 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'));
});
Częste błędy
Wpisywanie nazwy folderu w URL. Przy app.use(express.static('public')) URL to /style.css, a nie /public/style.css. Jeśli chcesz mieć /public w URL, podepnij go: app.use('/public', express.static(...)).
Linkowanie plików w HTML ścieżkami względnymi. <link href="style.css"> na stronie /blog/post prosi o /blog/style.css. Zaczynaj ścieżkę od ukośnika: /style.css.
Trzymanie sekretów w folderze statycznym. Wszystko w nim jest publiczne. Pliki konfiguracyjne, klucze i kod źródłowy trzymaj poza nim.
Najczęściej zadawane pytania
Czy mogę użyć express.static więcej niż raz?
Tak. Zarejestruj jeden na folder; Express szuka w nich w kolejności dodania i serwuje pierwszy znaleziony plik. Daj każdemu własną ścieżkę podpięcia, jeśli w folderach mogą być pliki o tej samej nazwie.
Czy Express powinien serwować pliki statyczne na produkcji?
W małych aplikacjach to w porządku. Przy dużym ruchu postaw przed nim CDN albo reverse proxy, np. Nginx, które serwuje pliki szybciej i cache'uje je bliżej użytkowników, a Express zostaw dla API.
Jak wysłać jeden konkretny plik z trasy?
Użyj res.sendFile(path.join(__dirname, 'files', 'report.pdf')). Ścieżka musi być absolutna (albo przekazujesz opcję root). Użyj res.download(), gdy przeglądarka ma zapisać plik zamiast go wyświetlić.