diff --git a/.gitignore b/.gitignore index 3b6f819..01c4888 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,13 @@ frontend/site/out/ frontend/site/dist/ frontend/admin/dist/ +# nginx 설정: 서버마다 다르므로 실제 파일은 커밋하지 않는다. 템플릿만 커밋한다. +# ★ compose(docker-compose.yml:224)가 ./nginx/site.conf 를 bind mount 한다 — +# 파일이 없으면 Docker 가 그 자리에 **디렉토리를 만들어** nginx 가 설정 없이 뜬다. +# 클론 직후 반드시: cp nginx/site.conf.example nginx/site.conf +nginx/site.conf +!nginx/site.conf.example + # OS .DS_Store diff --git a/nginx/site.conf.example b/nginx/site.conf.example new file mode 100644 index 0000000..9f086c1 --- /dev/null +++ b/nginx/site.conf.example @@ -0,0 +1,79 @@ +# 발행 사이트 정적 서빙. +# +# ★ 산출물은 named volume(site-out)으로 들어온다. 프리렌더 컨테이너가 쓰고 여기서 읽기만 한다 — +# 호스트 경로가 등장하지 않으므로 재배포로 코드를 갈아엎어도 사이트가 죽지 않는다. +# +# ★ 규칙은 하나뿐이다: 디렉토리 요청 → index.html. +# `/s/butter` (끝 슬래시 없음)가 사장님이 주소창에 치는 형태다. 이게 404 면 +# "발행했는데 안 나온다"가 된다. + +server { + listen 80; + listen [::]:80; + server_name _; + + # ★ nginx 이미지의 기본 문서 루트(/usr/share/nginx/html)를 쓰지 않는다. + # named volume 을 거기 마운트하면 Docker 가 **이미지에 들어 있던 index.html 을 + # 빈 볼륨으로 복사한다** — 그러면 오리진 루트가 "Welcome to nginx!" 를 띄우고, + # 그게 크롤러에 잡힌다. 빈 경로에 마운트하면 복사될 것이 없다. + root /srv/sites; + + # index 지시를 끈다. 오리진 루트에는 페이지가 없다(사이트는 /s/ 아래에 있다). + index index.html; + + charset utf-8; + server_tokens off; + + # 텍스트 산출물은 압축이 크게 먹는다(HTML 55KB → 10KB 안팎). + gzip on; + gzip_comp_level 6; + gzip_min_length 1024; + gzip_vary on; + gzip_types + text/plain text/css text/xml + application/javascript application/json application/xml + image/svg+xml; + + # ── 공용 번들 ────────────────────────────────────────────── + # 파일명에 해시가 박혀 있다. 내용이 바뀌면 이름이 바뀌므로 영구 캐시가 안전하다. + # ★ expires 와 add_header 를 함께 쓰면 Cache-Control 헤더가 두 줄로 나간다. + # 합쳐서 읽히긴 하지만 의도가 흐려지므로 add_header 하나로 통일한다. + location /assets/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + access_log off; + try_files $uri =404; + } + + location /fonts/ { + add_header Cache-Control "public, max-age=604800"; + access_log off; + try_files $uri =404; + } + + # ── 크롤러가 읽는 파일 ───────────────────────────────────── + # 발행하면 곧바로 반영되어야 한다. 길게 캐시하면 새 사업장이 사이트맵에 들어가도 + # 크롤러가 옛 파일을 계속 본다. + location = /robots.txt { + add_header Cache-Control "public, max-age=300, must-revalidate"; + } + + location = /sitemap.xml { + add_header Cache-Control "public, max-age=300, must-revalidate"; + } + + # ── 발행 사이트 ──────────────────────────────────────────── + location / { + # $uri/ 를 거치면 nginx 가 끝 슬래시로 301 을 내보낸다. 크롤러가 리다이렉트를 + # 한 번 더 타야 하므로 index.html 을 바로 준다. + try_files $uri $uri/index.html =404; + add_header Cache-Control "public, max-age=300, must-revalidate"; + } + + # 발행되지 않은 주소. 사장님이 오타를 냈을 때 흰 화면 대신 이유를 보여준다. + error_page 404 /404.html; + location = /404.html { + internal; + return 404 '페이지를 찾을 수 없습니다

페이지를 찾을 수 없습니다

주소를 다시 확인해 주세요.

'; + add_header Content-Type "text/html; charset=utf-8"; + } +}