diff --git a/.changeset/lighthouse-smoke-authority-guides.md b/.changeset/lighthouse-smoke-authority-guides.md new file mode 100644 index 000000000..2576bea3b --- /dev/null +++ b/.changeset/lighthouse-smoke-authority-guides.md @@ -0,0 +1,5 @@ +--- +'@app/ratewise': patch +--- + +Lighthouse CI smoke paths 擴充 3 篇 Authority Guide 內容頁,PR 性能守門同步覆蓋主戰內容頁;GA E2E 補強 hostname gate 回歸監測窗。純測試與 CI 覆蓋改善,無使用者可見行為變更。 diff --git a/apps/ratewise/seo-paths.config.mjs b/apps/ratewise/seo-paths.config.mjs index cd7b81ab4..d98776b7e 100644 --- a/apps/ratewise/seo-paths.config.mjs +++ b/apps/ratewise/seo-paths.config.mjs @@ -40,9 +40,17 @@ export const CONTENT_SEO_PATHS = [ * Lighthouse CI smoke audit paths. * * Keep this list small enough for PR latency, but canonical and representative: - * homepage app shell, FAQ structured-data page, and About E-E-A-T page. + * homepage app shell, FAQ structured-data page, About E-E-A-T page, + * and the 3 Authority Guide content pages (issue #344). */ -export const LIGHTHOUSE_CI_SMOKE_PATHS = ['/', '/faq/', '/about/']; +export const LIGHTHOUSE_CI_SMOKE_PATHS = [ + '/', + '/faq/', + '/about/', + '/sell-rate-vs-mid-rate/', + '/cash-vs-spot-rate/', + '/card-rate-guide/', +]; /** 法律頁:需預渲染,但維持 noindex,不納入 sitemap。 */ export const LEGAL_SSG_PATHS = ['/privacy/']; diff --git a/apps/ratewise/src/config/__tests__/seo-paths.test.ts b/apps/ratewise/src/config/__tests__/seo-paths.test.ts index 9a3abf67f..e79b084bd 100644 --- a/apps/ratewise/src/config/__tests__/seo-paths.test.ts +++ b/apps/ratewise/src/config/__tests__/seo-paths.test.ts @@ -124,7 +124,14 @@ describe('SEO Paths Configuration', () => { it('Lighthouse CI smoke paths 應使用 canonical trailing slash 路徑', () => { expect(APP_CONFIG.lighthouseSmokePaths).toEqual(LIGHTHOUSE_CI_SMOKE_PATHS); - expect(LIGHTHOUSE_CI_SMOKE_PATHS).toEqual(['/', '/faq/', '/about/']); + expect(LIGHTHOUSE_CI_SMOKE_PATHS).toEqual([ + '/', + '/faq/', + '/about/', + '/sell-rate-vs-mid-rate/', + '/cash-vs-spot-rate/', + '/card-rate-guide/', + ]); LIGHTHOUSE_CI_SMOKE_PATHS.forEach((path) => { expect(SEO_PATHS).toContain(path); diff --git a/apps/ratewise/src/config/seo-paths.ts b/apps/ratewise/src/config/seo-paths.ts index 25d2bbae0..c79bd3544 100644 --- a/apps/ratewise/src/config/seo-paths.ts +++ b/apps/ratewise/src/config/seo-paths.ts @@ -35,9 +35,17 @@ export const CONTENT_SEO_PATHS = [ * Lighthouse CI smoke audit paths. * * Keep this list small enough for PR latency, but canonical and representative: - * homepage app shell, FAQ structured-data page, and About E-E-A-T page. + * homepage app shell, FAQ structured-data page, About E-E-A-T page, + * and the 3 Authority Guide content pages (issue #344). */ -export const LIGHTHOUSE_CI_SMOKE_PATHS = ['/', '/faq/', '/about/'] as const; +export const LIGHTHOUSE_CI_SMOKE_PATHS = [ + '/', + '/faq/', + '/about/', + '/sell-rate-vs-mid-rate/', + '/cash-vs-spot-rate/', + '/card-rate-guide/', +] as const; /** 法律頁:需預渲染,但維持 noindex,不納入 sitemap。 */ export const LEGAL_SSG_PATHS = ['/privacy/'] as const; diff --git a/apps/ratewise/tests/e2e/ga-defer-lcp.spec.ts b/apps/ratewise/tests/e2e/ga-defer-lcp.spec.ts index ca79d698e..6512d9bb5 100644 --- a/apps/ratewise/tests/e2e/ga-defer-lcp.spec.ts +++ b/apps/ratewise/tests/e2e/ga-defer-lcp.spec.ts @@ -37,6 +37,25 @@ const APP_ROOT = fileURLToPath(new URL('../..', new URL('.', import.meta.url))); // 期望 config 次數依「建置內嵌 GA ID 且 執行 host 為正式站」決定,hostname 來源與 runtime 同一 SSOT。 const IS_PRODUCTION_HOST = new URL(BASE).hostname === PRODUCTION_HOSTNAME; +// gate 回歸監測窗(PR #629 review):initGA 於 load 後 ANALYTICS_INIT_DELAY_MS 再經 +// requestIdleCallback(timeout ANALYTICS_IDLE_TIMEOUT_MS)才執行;取樣需晚於此窗口, +// 否則 hostname gate 回歸時 GA 仍會在 6 秒後初始化而測試已提前通過。 +// 預設值鏡射 src/config/performance.ts(該檔依賴 import.meta.env,node 端無法直接 import)。 +const parsePositiveInteger = (value: string | undefined, fallback: number): number => { + if (value === undefined || value.trim() === '') return fallback; + const parsed = Number.parseInt(value, 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; +}; +const ANALYTICS_INIT_DELAY_MS = parsePositiveInteger( + process.env['VITE_ANALYTICS_INIT_DELAY_MS'], + 6_000, +); +const ANALYTICS_IDLE_TIMEOUT_MS = parsePositiveInteger( + process.env['VITE_ANALYTICS_IDLE_TIMEOUT_MS'], + 2_000, +); +const GATE_REGRESSION_WAIT_MS = ANALYTICS_INIT_DELAY_MS + ANALYTICS_IDLE_TIMEOUT_MS + 1_000; + function detectBuiltGaRuntime(): boolean { try { const assetDir = join(APP_ROOT, 'dist', 'assets'); @@ -188,7 +207,12 @@ test.describe('GA4 延後初始化 + LCP 效能', () => { null, { timeout: 10_000 }, ); + } else if (HAS_BUILT_GA_RUNTIME) { + // 建置內嵌 GA ID 但非正式站 host:等待超過完整 analytics 初始化窗口再取樣, + // 確保 hostname gate 回歸(#606)時能觀測到延後出現的 config 而非提前通過。 + await page.waitForTimeout(GATE_REGRESSION_WAIT_MS); } else { + // 建置未內嵌 GA ID:initGA 無 ID 可用,任何時點都不可能送 config,短取樣即可。 await page.waitForTimeout(300); } diff --git a/docs/SEO_MASTER_SSOT.md b/docs/SEO_MASTER_SSOT.md index d2dfb6ebb..650dee188 100644 --- a/docs/SEO_MASTER_SSOT.md +++ b/docs/SEO_MASTER_SSOT.md @@ -1805,43 +1805,43 @@ HaoRate 已具備高成熟度的技術 SEO 基礎。2026-04-10 審查結論:** ### 🟢 已完成(2026-03-23 → 2026-04-10) -| # | 任務 | 完成版本 | 說明 | -| ----- | -------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | -| P0-1 | 修正 llms.txt 金額頁 URL 格式描述(`?amount=` → 路徑式說明) | v2.16.0 | `generate-llms-txt.mjs` 改為 SSG 路徑型描述 | -| P0-2 | llms.txt AI/LLM Access Control 加入 `Claude-User` | v2.16.0+ | llms.txt 已更新說明 | -| P0-3 | robots.txt 明確 Allow `ClaudeBot`、`Claude-User`、`Claude-SearchBot` | 已驗證 | `generate-robots-txt.mjs` 已包含三條 Anthropic 爬蟲規則 | -| — | AnswerCapsule 元件實作 | v2.16.4 | `src/components/AnswerCapsule.tsx` + SEOPageMetadata.answerCapsule | -| — | AuthorityGuidePage 傳遞 jsonLd/faqContent/answerCapsule | v2.16.4 | 三篇 Guide 頁已具備接收 schema 的 props 管道 | -| — | Guide 頁加入 answerCapsule(2 題) | v2.16.4 | GUIDE_PAGE_SEO.answerCapsule 加入現金/即期常見問答 | -| — | 品牌 SSOT 收斂(manifest、PWA、llms、API 契約) | v2.16.1-2 | "HaoRate 匯率好工具" 品牌全面統一 | -| — | FAQ 頁 pathname 修正(`/faq` → `/faq/`) | v2.16.0 | canonical URL trailing slash 已補 | -| — | seo-health-check decodeURIComponent 中文標點修正 | v2.16.0 | 307 項健檢通過,0 錯誤 | -| — | amount 頁 canonical 與 schema URL 穩定性 | v2.16.0 | prerender HTML 回歸測試補強 | -| — | seo-static.ts 抽出(health-check 與 seo-metadata 共用標題常數) | v2.16.4 | 移除 health-check 對 Vite runtime 的直接依賴 | -| — | health-check 5xx 暫時性錯誤重試機制 | v2.16.4 | 避免短暫部署抖動誤報 | -| — | 金額頁數量修正(204 → 206) | v2.16.0 | CURRENCY_AMOUNT(104) + REVERSE_CURRENCY_AMOUNT(102) | -| — | SpeakableSpecification schema 補齊所有 9 個內容頁 | v2.18.0 | GUIDE/OPEN_DATA/ABOUT/三篇Authority Guide 頁;`buildSpeakableJsonLd(['h1'])` 加入各頁 jsonLd 陣列 | -| — | Organization + Person `knowsAbout` 實體權威信號 | v2.18.0 | `buildSiteJsonLd()` Organization 加入 12 個核心主題;`buildPersonJsonLd()` 加入 11 個作者知識領域 | -| — | Lighthouse CI 效能門檻調降至 0.83(自然波動緩衝) | v2.18.0 | `.lighthouserc.json` 從 0.85 降至 0.83;反映 knowsAbout JSON-LD 加入後的真實基準 | -| — | prebuild 外部 API 硬依賴修復(`SEO_RATE_EXAMPLES_OPTIONAL=1`) | v2.17.x | 第三方 API 短暫失敗時保留既有生成檔,不中止整個 build | -| — | fallback 匯率快照新鮮度檢查(> 24h 拒絕使用) | v2.17.x | `prebuild-fetch-rates.mjs` 加入時間戳解析,避免 stale 匯率寫入 SSG 頁面 | -| P0-4 | 加入 `CurrencyConversionService` schema 至首頁 JSON-LD | v2.22.0 | `seo-metadata.ts` buildCurrencyConversionServiceJsonLd;AI 引擎匹配「幣別換算」查詢時優先引用 | -| P0-5 | 加入 `ExchangeRateSpecification` schema 至所有 34 幣對頁 | v2.22.0 | `seo-metadata.ts` buildExchangeRateSpecificationJsonLd;從 `seo-rate-examples.ts` 動態讀取匯率 | -| P0-6 | 在所有幣對頁與金額頁加入可見更新時間戳 | v2.22.0 | `CurrencyLandingPage.tsx` 加入 `