공개 제보 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
54 lines
2.0 KiB
TypeScript
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);
|
|
}
|