merge: 프런트엔드 해시 자산 캐시 정책 반영
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user