Menu

express.static으로 Express 정적 파일 제공하기

app.use(express.static(path.join(__dirname, 'public')))는 public 폴더의 모든 파일을 같은 URL로 제공합니다. public/style.css는 /style.css에 응답합니다. 접두사 아래에 두려면 마운트 경로를 추가하세요.

이 페이지에는 실행 가능한 에디터가 있습니다. 편집하고 실행하면 결과를 바로 볼 수 있습니다.

express.static은 폴더의 파일을 제공하는 내장 미들웨어입니다. app.use(express.static(path.join(__dirname, 'public')))를 쓰면 public의 모든 파일을 URL의 같은 경로로 쓸 수 있습니다. public/style.css는 /style.css에, public/index.html은 /에 응답합니다. Express는 파일 확장자로 Content-Type을 정하고, 일치하는 파일이 없으면 404로 응답하는 대신 요청을 여러분의 라우트로 넘깁니다.

GET /style.css에 대한 응답은 파일 텍스트이고, Headers 탭에 Content-Type: text/css; charset=utf-8이 보입니다. GET /를 보내면 index.html을 받고, 이어서 GET /api/time을 보내면 그런 파일이 없으므로 라우트가 응답합니다.

폴더 경로에 __dirname이 필요한 이유

express.static('public')은 'public'을 server.js가 있는 폴더가 아니라 Node.js를 시작한 폴더 기준으로 해석합니다. 프로젝트 안에서 node server.js를 실행하면 동작하지만, 프로세스 매니저나 스크립트가 다른 곳에서 앱을 시작하면 깨집니다.

// 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')은 Node.js를 어디서 시작했든 그 줄이 있는 파일 옆의 폴더입니다. ES 모듈에서는 path.join(import.meta.dirname, 'public')을 쓰세요(Node 20.11 이후). __dirname과 path.join은 path 모듈 페이지에서 자세히 다룹니다.

접두사 아래에서 파일 제공하기

마운트 경로를 먼저 넘기면 폴더를 URL 접두사 아래에서 제공합니다. 접두사는 디스크의 파일 경로에 포함되지 않습니다.

GET /assets/app.js는 console.log("hi");를 text/javascript로 반환하고, GET /assets/img/dot.svg는 하위 폴더까지 닿습니다. 접두사 없이 GET /app.js를 보내면 404입니다. 파일은 /assets 아래에만 있기 때문입니다. 파일 URL이 API 라우트와 부딪히지 않게 하려면 접두사를 쓰세요.

maxAge로 캐시 헤더 설정하기

기본적으로 Express는 Cache-Control: public, max-age=0을 보내므로, 브라우저는 쓸 때마다 서버에 확인합니다(파일이 바뀌지 않았다면 ETag 헤더 덕분에 빠른 304 Not Modified를 받습니다). app.3f9a1c.js처럼 빌드마다 이름이 바뀌는 파일이라면 브라우저가 보관하게 하세요.

Headers 탭을 열면 Cache-Control이 이제 public, max-age=604800, 즉 7일을 초로 나타낸 값입니다. index: false는 폴더 URL에 index.html을 제공하는 기능을 끕니다. 다른 유용한 옵션으로는 /about이 about.html을 찾게 해 주는 extensions: ['html'], 그리고 기본값이 'ignore'라서 .env 같은 파일을 절대 제공하지 않는 dotfiles가 있습니다.

싱글 페이지 앱: index.html로 대체하기

클라이언트 라우팅을 쓰는 React나 Vue 앱은 /settings에서 새로고침해도 404가 나지 않도록, 모르는 경로에는 모두 index.html을 반환해야 합니다. 대체 라우트는 express.static과 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'));
});

흔한 실수

URL에 폴더 이름을 넣는 것. app.use(express.static('public'))이라면 URL은 /public/style.css가 아니라 /style.css입니다. URL에 /public을 넣고 싶다면 마운트하세요. app.use('/public', express.static(...))입니다.

HTML에서 상대 경로로 파일을 연결하는 것. /blog/post 페이지의 <link href="style.css">는 /blog/style.css를 요청합니다. 경로를 슬래시로 시작하세요. /style.css입니다.

정적 폴더에 비밀 값을 두는 것. 그 안의 모든 것은 공개됩니다. 설정 파일, 키, 소스 코드는 넣지 마세요.

자주 묻는 질문

express.static을 여러 번 써도 되나요?

네. 폴더마다 하나씩 등록하면 Express가 추가한 순서대로 찾아서 처음 발견한 파일을 제공합니다. 폴더에 같은 이름의 파일이 있을 수 있다면 각각 다른 마운트 경로를 주세요.

운영 환경에서 Express가 정적 파일을 제공해야 하나요?

작은 앱이라면 괜찮습니다. 트래픽이 많다면 CDN이나 Nginx 같은 리버스 프록시를 앞에 두세요. 파일을 더 빠르게 제공하고 사용자 가까이에 캐시합니다. Express는 API에 집중시키세요.

라우트에서 특정 파일 하나를 보내려면?

res.sendFile(path.join(__dirname, 'files', 'report.pdf'))를 쓰세요. 경로는 절대 경로여야 합니다(또는 root 옵션을 넘깁니다). 브라우저가 파일을 보여 주지 않고 저장하게 하려면 대신 res.download()를 쓰세요.

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기