diff --git a/Dockerfile b/Dockerfile index fb7be2a..0df9f43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,24 @@ +# ---- 자산 URL 에 내용 해시를 박는 단계 ---- +# 프록시(NPM)가 js/css 응답에 제 캐시 헤더(Expires, max-age)를 덧붙이므로 +# 원본에서 no-cache 를 줘도 브라우저가 옛 파일을 계속 쓴다. 내용이 바뀌면 +# URL 도 바뀌게 해 두어야 새 배포가 제대로 반영된다. +FROM alpine:3.20 AS stamp +WORKDIR /site +COPY site/ ./ +RUN set -eu; \ + v=$(cat assets/crossword.css assets/crossword.js | sha256sum | cut -c1-10); \ + for f in *.html; do \ + sed -i -e "s|assets/crossword\.css|assets/crossword.css?v=${v}|g" \ + -e "s|assets/crossword\.js|assets/crossword.js?v=${v}|g" "$f"; \ + done; \ + echo "asset stamp: ${v}"; \ + grep -o 'assets/crossword\.[a-z]*?v=[0-9a-f]*' korean.html + FROM nginx:1.27-alpine # 기본 서버 설정 제거 후 8080 포트용 설정 적용 RUN rm -f /etc/nginx/conf.d/default.conf COPY nginx/default.conf /etc/nginx/conf.d/default.conf -COPY site/ /usr/share/nginx/html/ +COPY --from=stamp /site/ /usr/share/nginx/html/ EXPOSE 8080 diff --git a/README.md b/README.md index 0624df4..5bd669a 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ | `site/assets/crossword.css` | 각 편이 공유하는 화면 양식 | | `site/assets/crossword.js` | 각 편이 공유하는 게임 엔진 (한글 IME 입력, 검사/힌트/타이머) | | `nginx/default.conf` | nginx 설정 (8080 리슨, `/healthz` 헬스체크) | -| `Dockerfile` | nginx:1.27-alpine 정적 서빙 이미지 | +| `Dockerfile` | 자산 URL 에 내용 해시를 박는 단계 + nginx:1.27-alpine 정적 서빙 | | `k8s/deployment.yaml` | Deployment + NodePort Service (31380) | | `Jenkinsfile` | Checkout → 이미지 빌드/푸시 → kubectl apply | @@ -65,6 +65,18 @@ nginx 로 파일만 내려주면 끝. 낱말 풀은 작품과 작가만으로는 성글다. 특히 원어 표기의 음절이 서로 겹치지 않는 편(독일 편)에서는 **등장인물과 작품 속 핵심어까지 넣어야** 교차 지점이 생긴다. +## 캐시 + +프록시(NPM)가 `js` / `css` 응답에 제 캐시 헤더를 덧붙인다. 원본 nginx 가 +`no-cache` 를 줘도 덮어써지므로, 브라우저가 옛 자산을 계속 쓰는 일을 막기 위해 +**이미지 빌드 때 자산 URL 에 내용 해시를 박는다.** + +- `Dockerfile` 의 `stamp` 단계가 `crossword.css` + `crossword.js` 의 sha256 앞 + 열 자리를 뽑아 각 페이지의 링크를 `assets/crossword.js?v=<해시>` 로 바꾼다 +- 자산 내용이 바뀌면 해시가 바뀌고 URL 도 바뀌므로 캐시가 저절로 무효화된다 +- 저장소의 HTML 은 해시 없는 상태로 두면 된다. 손으로 붙일 필요 없다 +- HTML 자체는 프록시가 캐시하지 않으므로 새 해시가 바로 전달된다 + ## 배포 Jenkins 잡(`crossword`)을 돌리면 아래가 순서대로 진행된다. diff --git a/nginx/default.conf b/nginx/default.conf index 890cb21..fe35ef3 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -15,9 +15,10 @@ server { gzip_min_length 1024; gzip_types text/plain text/css application/javascript application/json image/svg+xml; - # 단일 정적 페이지 - 어떤 경로로 들어와도 index.html + # 여러 페이지짜리 정적 사이트. 없는 경로는 index.html 로 되돌리지 말고 404 를 준다. + # (되돌리면 /없는페이지.html 이 200 으로 셸을 내려주어 배포 사고를 못 알아챈다) location / { - try_files $uri $uri/ /index.html; + try_files $uri $uri/ =404; add_header Cache-Control "no-cache, must-revalidate"; } diff --git a/site/russian.html b/site/russian.html index d9d761e..6f378fa 100644 --- a/site/russian.html +++ b/site/russian.html @@ -67,7 +67,7 @@