Files
Gnuboard7/resources/js/core/support/fetchStaticFirst.ts
T
HeuJung 5ba7a83597 feat(core,engine): 부트스트랩 리소스 정적 게시(bake) 및 폴백 체계 도입
공개 제보 https://github.com/gnuboard/g7/issues/122 대응 — 초기 부트스트랩
리소스(다국어 병합·컴포넌트 정의·라우트·확장 번들·템플릿 dist)를 캐시 버전
디렉토리(public/build/ext/{v}/)에 실파일로 게시해 웹서버가 rewrite 전에 직접
서빙한다. 부트 임계 경로의 PHP 왕복을 제거하고(실측 TTFB 131~144ms → 1~7ms),
미게시·부분게시·GC 직후에는 fetch·태그·번들 3계층이 종전 API 로 즉시 폴백한다.

- 게시: 원자적 tmp→rename→manifest(존재=완료), 캐시 락 단일 실행, 인라인 GC
 (현재+직전 1개), incrementExtensionCacheVersion 단일 지점 terminating 트리거
 + blade 자가 치유 + 설치기 태스크(best_effort) + 일일 cleanup 스케줄,
 sudo 업데이트 대비 소유권 정상화(normalizeOwnership)·prune/백업 제외
- 프론트(engine-v1.61.0): blade 주입 cache_version 1급 시드(이중 부트 로드 제거),
 fetchStaticFirst 즉시 폴백, ComponentRegistry 버전 키드 매니페스트,
 ModuleAssetLoader 번들 정적→레거시 폴백, asset-url-recovery staticToLegacy 역변환
- 폴백 API 품질: lang/components/routes ETag+304 + 환경 분기 Cache-Control,
 열화 라우트 스냅샷 공개 캐시 금지(서버측 캐시 회피와 대칭), 게시 .htaccess
 mod_deflate + nginx gzip 스니펫(압축 전송량 회귀 방지)
- SEO 정합: 봇 HTML 은 GC 대상 정적 URL 미사용(allowStatic:false), props $switch
 봇측 해석 구현(engine-v1.56.0 패리티), 패리티 룰 expression-dialect 그룹 신설,
 상주 allow 헤더 제거로 잠금 복원, _comment* 접두 주석 키 분류
- 검증: 전 수정 red→green 4단계, Playwright 라이브 21건, Chrome MCP 24축+M1~M3,
 봇 curl 3축, 캐시 저장소(file/redis/database) 축 판정, 히스토리·공개이슈·커밋
 이력 전수 재조사 반영
- 코어 7.0.10, engine-v1.61.0. kill-switch: G7_STATIC_CACHE=false
2026-08-25 17:02:53 +09:00

54 lines
2.0 KiB
TypeScript

/**
* 정적 게시(bake) 우선 fetch 헬퍼 (#122).
*
* 부트스트랩 리소스(routes/lang/components)는 정적 게시본(`/build/ext/{v}/…`)을
* 먼저 시도하고, 응답이 `!ok`(부분 게시·GC 직후 404 포함)이거나 네트워크 실패면
* **즉시** 종전 API URL 로 폴백한다. 폴백은 관측 가능해야 자가 치유 실패를
* 발견할 수 있으므로 warn 1줄을 남긴다 (조용한 폴백 금지).
*
* legacy 측은 `fetchWithRetry` 를 재사용해 종전의 네트워크 복원력(#463)을 유지한다.
*
* @since engine-v1.61.0
*/
import { fetchWithRetry, type RetryOptions } from '../template-engine/networkResilience';
/**
* 정적 URL 우선 + legacy API 폴백 fetch.
*
* @param staticUrl 정적 게시 URL (null 이면 legacy 직행 — staticBase 미주입)
* @param legacyUrl 종전 API URL
* @param options legacy 측 재시도 옵션 + fetch init (정적 측은 init 만 사용)
* @returns HTTP 응답 (legacy 측은 4xx/5xx 포함 — 호출부의 기존 분기 보존)
*/
export async function fetchStaticFirst(
staticUrl: string | null,
legacyUrl: string,
options: RetryOptions & { init?: RequestInit } = {}
): Promise<Response> {
if (!staticUrl) {
return fetchWithRetry(legacyUrl, options);
}
try {
const response = await fetch(staticUrl, options.init);
if (response.ok) {
return response;
}
// 디버그 게이트 없는 console.warn — 폴백은 자가 치유 실패를 발견할 유일한
// 신호라 프로덕션 콘솔에서도 보여야 한다 (bootstrap 인라인 재시도와 동일 사상)
console.warn(
`[fetchStaticFirst] Static fast path miss (${response.status}) — falling back to API: ${staticUrl}`
);
} catch (error) {
console.warn(
`[fetchStaticFirst] Static fast path fetch failed — falling back to API: ${staticUrl}`,
error
);
}
return fetchWithRetry(legacyUrl, options);
}