perf(caddy): 해시 빌드 자산에 장기 캐시를 적용

Vite content hash가 포함된 frontend asset만 1년 immutable로 제공한다. HTML, hash 없는 asset과 별도 image 경로는 기존 재검증 정책을 유지하고 matcher 회귀 테스트와 운영 문서를 추가한다.
This commit is contained in:
2026-08-18 12:55:07 +00:00
parent 08bd24909e
commit 7af9c88c27
3 changed files with 55 additions and 0 deletions
+23
View File
@@ -134,6 +134,22 @@ Caddy의 runtime logger는 reverse proxy 오류에도 요청 정보를 남길
`X-Session-Token` 값을 encoder 단계에서 삭제합니다. 장애 로그를 수집할 때도
인증 헤더, cookie, token과 secret을 보고서나 채팅에 복사하지 않습니다.
## 프런트엔드 캐시 정책
Gateway와 profile frontend의 Vite build는 `/gateway/assets/`,
`/<profile>/assets/` 아래 파일명에 8자리 content hash를 붙입니다. 내부 Caddy는
이 형식을 만족하는 JS, CSS, source map과 기타 build asset에
`Cache-Control: public, max-age=31536000, immutable`을 적용합니다. 새 build는
내용이 달라지면 URL도 달라지므로 배포와 rollback 뒤에도 기존 URL의 장기 cache를
재사용할 수 있습니다.
`index.html`, router fallback, `terms.*.html`처럼 URL이 고정된 HTML은 이 정책에
포함하지 않습니다. 이 응답은 Vite preview의 `Cache-Control: no-cache`와 ETag를
유지하여 저장은 허용하되 사용할 때마다 변경 여부를 재검증합니다. content hash가
없는 `/assets/` 파일과 별도 운영 자산인 `/image/*``immutable`로 취급하지
않습니다. 따라서 public directory에 장기 cache할 파일을 추가할 때는 먼저
content-hashed URL로 옮겨야 합니다.
## 개발 bind 모드
로컬 Core2026 checkout을 container에 bind하고 DB/Redis/Caddy는 같은 구성으로
@@ -181,3 +197,10 @@ docker compose -f compose.yaml -f compose.smoke.yaml up -d --build --wait
다른 env 파일은 `ENV_FILE=/path/to/file ./scripts/check.sh`로 검사합니다.
`docker compose config` 전체 출력에는 펼쳐진 비밀값이 포함될 수 있으므로 CI
artifact나 이슈에 그대로 첨부하지 않습니다.
Caddy cache matcher의 적용·제외 경계는 다음처럼 비밀값 없이 별도로 검사할 수
있습니다.
```sh
node --test test/caddy-cache-policy.test.mjs
```
+5
View File
@@ -10,6 +10,11 @@
{$SITE_ADDRESS} {
encode zstd gzip
# Vite emits content-hashed files below each frontend's assets directory.
# Defer this header so it replaces vite preview's default no-cache value.
@immutableFrontendAssets path_regexp immutableFrontendAssets ^/(gateway|che|kwe|pwe|twe|nya|pya|hwe)/assets/.+-[A-Za-z0-9_-]{8}\.[^/]+$
header @immutableFrontendAssets >Cache-Control "public, max-age=31536000, immutable"
redir /gateway /gateway/ 308
redir /che /che/ 308
redir /kwe /kwe/ 308
+27
View File
@@ -0,0 +1,27 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
test('Caddy caches only content-hashed Vite frontend assets as immutable', async () => {
const caddyfile = await fs.readFile(new URL('../caddy/Caddyfile', import.meta.url), 'utf8');
const matcher = caddyfile.match(
/@immutableFrontendAssets path_regexp immutableFrontendAssets (\S+)/,
);
assert.ok(matcher, 'immutable frontend asset matcher must exist');
const assetPath = new RegExp(matcher[1]);
for (const profile of ['gateway', 'che', 'kwe', 'pwe', 'twe', 'nya', 'pya', 'hwe']) {
assert.equal(assetPath.test(`/${profile}/assets/index-CcXtLSVk.js`), true);
assert.equal(assetPath.test(`/${profile}/assets/index-CcXtLSVk.js.map`), true);
}
assert.equal(assetPath.test('/gateway/'), false);
assert.equal(assetPath.test('/gateway/terms.1.html'), false);
assert.equal(assetPath.test('/gateway/assets/index.js'), false);
assert.equal(assetPath.test('/image/game/figure.png'), false);
assert.match(
caddyfile,
/header @immutableFrontendAssets >Cache-Control "public, max-age=31536000, immutable"/,
);
});