SSR とハイドレーション不整合の検出と修正を自動化

shoelace-style/webawesome

SSR が有効な状態で、コンポーネントのハイドレーション時に lit-hydration-error が発生してビルドが失敗するケースが報告されていました。本 PR はその検出を自動化し、SSR の実装を安定させるためのコードと CI 変更をまとめています。

背景

SSR が有効な開発サーバーでドキュメントページをビルドすると、<wa-accordion> などのコンポーネントが子要素の更新を待たずに DOM 操作を行い、ハイドレーションミスマッチ が起きていました。これによりローカル環境でも CI でもテストが不安定になる問題がありました。

開発者は手動でページを確認してエラーを特定していたため、再現性のある自動テストが求められていました。PR の説明では「Playwright スクリプトでページをクロールし、エラーイベントをキャッチする」ことが解決策として提示されています。

技術的な変更

Playwright ハイドレーションチェックスクリプトの追加

packages/webawesome/scripts/check-hydration.js が新規追加され、SSR モードで全ドキュメントページを走査lit-hydration-error が発火したらプロセスが非ゼロで終了します。主要な設定は環境変数 BASE_URLCONCURRENCYRELOAD_ATTEMPTS で調整可能です。

import { globby } from 'globby';
import { chromium } from 'playwright';
// ...(省略)
const BASE_URL = (process.env.BASE_URL || 'http://localhost:4000').replace(/\/$/, '');

スクリプトはサーバー起動待ち (waitForServer) とページ読み込み後のカスタムイベント監視 (page.evaluate) を行い、エラーがあれば process.exit(1) で失敗させます。

CI ワークフローへの統合

.github/workflows/hydration_tests.yml が新規作成され、npm start & で SSR 開発サーバーをバックグラウンド起動し、続いて npm run test:hydration を実行します。node_modulesplaywright がインストールされるステップも追加されています。

- name: Start dev server
  run: npm start &
  working-directory: ./packages/webawesome

- name: Check docs pages for hydration errors
  run: npm run test:hydration
  working-directory: ./packages/webawesome

SSR 時のコンポーネントロジック修正

packages/webawesome/src/components/accordion/accordion.tshandleSlotChange に、SSR 後の子要素の updateComplete を待機するロジックが追加されました。didSSR が真の場合、子要素がハイドレーション完了するまで Promise.allSettled で待ち、完了後に再帰的に handleSlotChange を呼び出します。

if (this.didSSR) {
  const promises: Promise<boolean>[] = [];
  this.getAllItems().forEach(item => {
    if (item.didSSR && !item.hasUpdated) {
      promises.push(item.updateComplete);
    }
  });
  if (promises.length > 0) {
    Promise.allSettled(promises).then(() => this.handleSlotChange());
    return;
  }
}

同様に docs/.eleventy.js では SSR が有効なときだけ globalThis.litSsrCallConnectedCallback = true を設定し、SimulateWebAwesomeAppssr フラグを渡すように変更されています。

if (!isDev && !serverBuild) {
  const ssr = process.env.SSR === 'true';
  if (ssr) {
    globalThis.litSsrCallConnectedCallback = true;
  }
  eleventyConfig.addTransform('simulate-webawesome-app', function (content) {
    return SimulateWebAwesomeApp(content, { isDev: isDev, ssr });
  });
}

ビルドスクリプトへのハイドレーションテスト呼び出し

packages/webawesome/scripts/build.jscookie-parser ミドルウェアの登録と、SSR ビルド時に initLitSsr() を呼び出す処理が挿入され、check-hydration.js が CI でも実行可能になる基盤が整備されました。

import cookieParser from 'cookie-parser';
// ...
middleware: [
  cookieParser(),
  function simulateWebawesomeApp(req, res, next) { /* ... */ }
]

テストランナー設定の拡張

web-test-runner.config.js でブラウザリストが動的に構成され、CI では Chromium と Firefox、ローカルでは WebKit も含めてマルチブラウザでハイドレーションテストが走ります。

const browsers = [
  playwrightLauncher({ product: 'chromium', concurrency }),
  playwrightLauncher({ product: 'firefox', concurrency }),
];
if (process.env.CI !== 'true') {
  browsers.push(playwrightLauncher({ product: 'webkit', concurrency }));
}

設計判断

エラーディテクションをコード側と CI 側の二層で検出

SSR のハイドレーションエラーは コンポーネント側での非同期更新待ちCI での自動クロールの二段階で捕捉できる設計となっています。コンポーネント修正は子要素の完了待ちロジックだけで済み、既存 API への侵害はありません。

後方互換性と最小侵入性

handleSlotChange の追加は SSR 時だけ条件分岐し、CSR では従来通りの同期ロジックが走ります。また globalThis.litSsrCallConnectedCallback の設定は SSR が有効なときに限り有効化するため、開発サーバーのデフォルト挙動を変えません。

設定とテストの分離

Playwright スクリプトは環境変数で対象 URL・並列度・リロード回数を制御でき、ローカルと CI の負荷に合わせて柔軟に調整可能です。CI ワークフローは独立したジョブとして追加され、他のテストパイプラインに影響を与えない構成になっています。

まとめ

この PR では SSR 時に発生したハイドレーションミスマッチを 自動検出スクリプトコンポーネント側の待機ロジック で解決し、CI にハイドレーションテストを組み込むことでリグレッションを防止します。既存コードへの影響は最小限に抑えつつ、開発フロー全体の信頼性が大きく向上しました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
77cb02d8

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文がタイトル直下にあり、背景・技術的な変更・まとめが明確に区分されている。まとめは単なる繰り返しではなく、変更の意義と影響を述べている。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックは `言語:ファイルパス` の形式で正しく記述されている。一方、PRリンクは `[PR #2501](URL)` となっており、仕様の `[#2501](URL)` とは若干形式が異なるため警告とした。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

内容は SSR やハイドレーションといった専門用語を前提としたエンジニア向けで、初心者向けの余計な説明はない。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論→各論→結論の流れで構成され、段落はトピックセンテンスで始まり1トピックに絞られている。段落の長さも適切で、空行で区切られている。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは Diff に記載されたファイルと内容が一致しており、重要な変更点(workflow、check‑hydration スクリプト、accordion 修正等)が正確に反映されている。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

SSR、hydration、Playwright などの用語は PR の記述と合致し、誤用は見られない。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な主張は PR の説明と Diff の変更点に裏付けられており、誤った情報はない。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の主張はすべて PR タイトル・説明・Diff に基づいており、捏造や推測は含まれていない。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #2501 が正しく記載されているほか、数値的な誤りはない。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の主旨(SSR とハイドレーションミスマッチの自動検出・修正)を適切に要約している。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

LTS やリリース日程など、PR には記載されていない外部情報は一切含まれていない。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と一致している。