노트

nginx로 SPA 배포

Serving a Single-Page Application with nginx

인프라#http · 연결된 개념 7개

쉽게 말하면

nginx로 SPA를 배포하면 nginx가 박물관 안내원처럼 실제로 있는 전시물은 바로 보여 주고, 없는 방을 물으면 안내 지도가 있는 입구로 보내요. 그 지도가 index.html이고 길 찾기는 브라우저의 라우터가 맡아요.

비유가 깨지는 곳 안내원이 없으면 /users/1에서 새로고침할 때 서버가 그런 파일은 없다며 404를 내요. 또 해시가 붙은 정적 파일은 길게 캐시하되 index.html은 짧게 둬야 새 배포가 바로 보여요.

빌드된 SPA(Single-Page Application: React·Vue 등)는 정적 파일 묶음이라 nginx 같은 웹 서버로 바로 서빙할 수 있다. 핵심은 try_files로 없는 경로를 index.html로 돌려보내 클라이언트 라우터(client-side router)가 주소를 처리하게 하는 것이다.

server {
  listen 80;
  server_name example.com;
  root /var/www/app/dist;
  index index.html;
 
  location / {
    try_files $uri $uri/ /index.html;
  }
 
  location /assets/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }
}
  • try_files $uri $uri/ /index.html: 요청한 파일이 있으면 그 파일, 디렉터리면 그 안의 index, 둘 다 없으면 index.html을 준다. 이게 없으면 /users/1에서 새로고침할 때 404가 난다
  • 해시가 붙은 정적 파일은 길게 캐시하고, index.html은 캐시를 짧게 두어 새 배포가 바로 보이게 한다(브라우저 캐시)
  • API는 같은 서버에서 location /api/ { proxy_pass http://backend:8080; }로 넘기면 출처가 같아져 CORS 설정이 필요 없다

운영 팁

  • 설정은 /etc/nginx/sites-available에 두고 sites-enabled로 링크하는 배포판이 많다. 바꾼 뒤 nginx -t로 검사하고 systemctl reload nginx
  • 500·403이 나면 nginx 작업 계정(www-data 등)이 정적 파일 경로를 읽을 수 있는지 본다. user root로 바꿔 해결하는 글이 있지만 권한을 넓히는 위험한 방법이다. 파일·상위 디렉터리 권한을 고친다
  • 다른 서비스가 80번을 쓰고 있다면 listen 포트를 바꾸거나 그 프로세스를 정리한다(포트를 쓰는 프로세스 찾아 종료하기)

HTTPS는 Let's Encrypt로 HTTPS 적용, 어떤 렌더링 방식이 정적 서빙에 맞는지는 CSR·SSR·SSG·ISR를 본다. 컨테이너로 묶을 때는 빌드 단계와 nginx 단계를 나눈 멀티 스테이지 빌드가 흔하다.

출처: nginx 문서: try_files

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 버전 스큐와 정적 자산 오프로드

    버전 스큐(Version Skew)는 새 버전을 배포한 뒤에도 브라우저에 떠 있는 이전 빌드의 클라이언트가 새 서버와 통신하면서 생기는 불일치다. 대표 증상은 이전 HTML이 참조하던 JS 청크를 늦게 불러오려다 404가 나는 ChunkLoadError다.

  • loading 경계와 레이아웃 끌어올리기

    Next.js App Router에서 loading.tsx는 같은 세그먼트의 page와 그 아래(하위 layout 포함)를 suspense로 감싸지만, **같은 세그먼트의 layout은 감싸지 않는다**. 그래서 데이터를 기다릴 필요가 없는 공통 UI(탭바, 필터 헤더)를 page에서 layout으로 끌어올리면 스켈레톤에 덮이지 않고 처음부터 실제 모습으로 보인다.

  • XSS

    XSS(Cross-Site Scripting)는 공격자가 넣은 스크립트가 내 사이트의 출처(origin) 권한으로 사용자 브라우저에서 실행되는 공격이다. 같은 출처의 코드로 돌기 때문에 same-origin-policy가 막아 주지 못한다. 그 스크립트는 페이지를 바꾸고, 로그인한 사용자 행세를 하며 요청을 보내고, JS가 읽을 수 있는 데이터(localStorage의 토큰 등)를 빼 갈 수 있다.

  • Pages Router에서 App Router로

    App Router는 Next.js 13에서 도입된 app/ 디렉터리 기반 라우터로, 서버 컴포넌트·중첩 레이아웃(Nested Layouts)·스트리밍을 기본으로 한다. pages/ 기반의 Pages Router에서 옮길 때 바뀌는 생각을 정리한다.

  • CDN

    CDN(Content Delivery Network)은 세계 곳곳에 둔 서버(엣지, edge)들이 원본 서버(오리진, origin)의 응답을 복사해 두었다가 사용자와 가까운 곳에서 대신 내주는 네트워크다. 물리적 거리가 줄어 응답이 빨라지고, 오리진으로 가는 요청이 줄어 서버 부담도 줄어든다.

보기 옵션