はじめに

PageSpeed Insightsでトップページだけ100点を取るのは、それほど難しくありません。インラインCSS、最小限のDOM、画像なし。条件を揃えれば達成できます。

本当に難しいのは、サイト内の全ページタイプで100点を維持することです。記事詳細ページには動的コンテンツがあり、チームページにはスタッフカードのグリッドがあり、ツールページにはインタラクティブなJavaScriptがあります。ページタイプごとに異なる課題が潜んでいるのです。

2024年3月、GoogleはCore Web Vitalsの指標を更新し、FID(First Input Delay)を**INP(Interaction to Next Paint)**に置き換えました。INPはページ上のすべてのインタラクションの応答性を測定するため、従来よりも厳格な指標です。2026年現在、Performance 100点の達成にはINPへの対応が前提条件となっています。

この記事では、京谷商会の18ポータルサイト基盤(Cloudflare Workers上のSSRアプリケーション)において、全7種のページタイプでPerformance・Accessibility・Best Practices・SEOの4項目すべて100点を達成するまでの過程を、実際のLighthouse監査結果と修正コードとともに記録します。

対象サイトの構成

今回の対象は、1つのCloudflare Workerが18のサブドメインを動的にルーティングするポータルシステムです。テンプレートエンジンを使わず、TypeScriptの文字列テンプレートリテラルでHTMLを直接生成するSSR構成を採用しています。

ページタイプは以下の7種類です。

ページタイプ URL例 特徴
トップページ / 記事カードグリッド、サムネイル画像
記事一覧 /articles/ ページネーション、カードリスト
記事詳細 /articles/{slug}/ Markdownレンダリング、目次、著者情報
チーム /team/ スタッフカードグリッド、アバター
スタッフ詳細 /team/{code}/ プロフィール、担当記事一覧
用語集 /glossary/ 五十音グループ、カードリスト
ツール /tools/ PageSpeed診断(SEOナレッジベースのみ)

トップページは既に全項目100点を達成済みでした。問題は残りの6タイプです。

発見された問題と修正

問題1:mainランドマークの欠如(Accessibility −9点)

Lighthouseの指摘landmark-one-main — 「ドキュメントにメインのランドマークが設定されていません」でした。

共通レイアウト関数 layout() では、ヘッダーとフッターの間にコンテンツを直接挿入していました。スクリーンリーダーは <main> 要素を使ってページの主要コンテンツ領域を特定するため、この要素がないとナビゲーション効率が大幅に低下します。WAI-ARIAのランドマーク仕様では、各ページに1つの main ランドマークを含めることを推奨しています。

修正内容として、layout() 関数内でコンテンツを <main> タグで囲みました。この1行の修正が全ページタイプに自動的に適用されます。共通レイアウト関数を持つアーキテクチャの強みです。

問題2:canonical URLのパス未設定(SEO −8点)

Lighthouseの指摘canonical — 「ドキュメントに有効な rel=canonical が指定されていません」でした。

<link rel="canonical"> タグ自体は存在していましたが、パス部分が常に / にフォールバックしていました。チームページ(/team/)のcanonicalがトップページ(/)を指している状態は、Lighthouseが「無効」と判定します。Google検索セントラルのcanonicalガイドでも、各ページが自己参照するcanonical URLを持つべきと明記されています。

原因は、パスを渡すためのグローバル変数がどこからも設定されていなかったことです。

修正内容として、Honoミドルウェアを追加し、全リクエストで現在のURLパスをグローバルに設定しました。Cloudflare Workersはリクエストごとに独立したコンテキストで実行されるため、グローバル変数の競合リスクは事実上ありません。12個のレンダリング関数すべてにパス引数を追加するよりも、変更量が圧倒的に少なく済むアプローチです。

問題3:テキスト内リンクの識別不能(Accessibility −7点)

Lighthouseの指摘link-in-text-block — 「リンクは色に依存して識別可能です」でした。

グローバルCSSで a { text-decoration: none; } を設定していたため、リンクと通常テキストの区別が色のみに依存していました。WCAG 2.1の達成基準1.4.1は、色だけに依存せずにリンクを識別できることを要求しています。

具体的には、パンくずリストのリンク色(#64748b)と周囲テキスト色(#4b5563)のコントラスト比が1.58:1しかなく、基準の3:1を大幅に下回っていました。

修正内容として、デフォルトのリンクスタイルをアンダーライン付きに変更し、ナビゲーション要素やカードリンクのみアンダーラインを除外する方針に転換しました。text-decoration-skip-ink: auto の指定により、アンダーラインが文字のディセンダー(g、y、pなどの下に伸びる部分)と重なる箇所で自動的に途切れ、視認性を維持しつつデザインの美しさを損ないません。

問題4:Coming Soonカードのコントラスト不足(Accessibility −7点)

Lighthouseの指摘color-contrast — 「背景色と前景色には十分なコントラスト比がありません」でした。

SEOナレッジベースのツール一覧ページには、開発中のツール2つが opacity: 0.5 のカードとして表示されていました。親要素の opacity は子要素すべてに継承されるため、テキストの実効コントラスト比が基準を下回ります。

修正内容として、opacity による「無効化」表現を廃止し、border-style: dashedcolor: var(--text-muted) の組み合わせに変更しました。破線ボーダーは「未完成」「準備中」を視覚的に伝える表現として直感的であり、コントラスト比の問題も発生しません。

問題5:ポータルごとのアクセントカラーとボタンコントラスト

18ポータルはそれぞれ異なるテーマカラー(--accent)を持っています。一部のポータルでは、background: var(--accent); color: #fff のボタンがコントラスト基準4.5:1を満たしていませんでした。

修正内容として、ボタンの背景色を var(--accent) から var(--primary-header) に変更しました。--primary-header はヘッダー背景にも使われる暗い色で、白文字との組み合わせで全ポータルにおいて十分なコントラスト比を確保できます。

INPへの対応 — Workers SSRの構造的優位性

2024年3月にCore Web Vitalsの応答性指標がFIDからINP(Interaction to Next Paint)に切り替わりました。FIDが「最初のインタラクションの入力遅延」だけを測定していたのに対し、INPページ上のすべてのインタラクション(クリック、タップ、キー入力)の中で最も遅い応答を測定します。つまり、1回でも重い処理があればスコアに直結します。

京谷商会のポータル基盤がINPで有利な理由は明確です。

クライアントサイドJavaScriptがほぼゼロであること。SSRで完成したHTMLを返すため、Reactのハイドレーションやバンドルの解析・実行といった重い処理が発生しません。インタラクションを阻害するメインスレッドのブロッキングが構造的に起こりにくい設計です。

イベントハンドラが最小限であること。ハンバーガーメニューの開閉やツールページのフォーム送信など、インタラクティブ要素は意図的に限定しています。各イベントハンドラも軽量なDOM操作のみで完結するため、200ms以内(INPの「良好」閾値)に余裕を持って収まります。

INPの閾値は200ms以下が「良好」、200〜500msが「改善が必要」、500ms超が「不良」です。Cloudflare Workers SSR構成では、重いクライアントサイドフレームワークを使わない限り、この閾値を超えることは事実上ありません。

修正の検証結果

PageSpeed Insights 全4項目100点のスコア表示

全修正をデプロイした後、Google PageSpeed Insights APInocache=1 オプション付きで呼び出し、キャッシュを無視した新鮮なスコアを取得しました。

ページタイプ ポータル Performance Accessibility Best Practices SEO
チーム SEO 100 100 100 100
用語集 SEO 100 100 100 100
ツール(SEO) SEO 100 100 100 100
記事一覧 SEO 100 100 100 100
記事詳細 SEO 100 100 100 100
ツール(NoTools) ADS 100 100 100 100
チーム CLD 100 100 100 100

全ページタイプ、複数ポータルにまたがって全項目100点を確認しました。

なお、Lighthouseのスコアはラボデータ(シミュレーション環境での計測)です。実際のユーザー体験を反映するフィールドデータChrome UX Report(CrUX)で確認できます。サイトのトラフィックがCrUXの閾値(過去28日間で十分なサンプル数)を満たすと、PageSpeed Insightsの上部にフィールドデータが表示されるようになります。ラボデータとフィールドデータの両方で良好なスコアを維持することが、真の意味でのパフォーマンス最適化です。

アーキテクチャが支えた効率的な改善

今回の改善で修正したファイルは実質2つだけです。全ページのHTMLテンプレートを含む render.ts と、ルートハンドラおよびミドルウェアの index.ts です。

1つの layout() 関数が全ページタイプの共通構造を生成しているため、<main> タグの追加やリンクスタイルの変更が自動的に全ページに反映されました。テンプレートの共通化は、保守性だけでなく品質の均一化にも直結します。

また、Cloudflare WorkersのSSR構成はPageSpeed Performanceの面でも有利です。エッジサーバーで即座にHTMLを生成して返すため、TTFB(Time to First Byte)が極めて短く、外部リソースへの依存もありません。JavaScriptフレームワークのハイドレーションコストもゼロです。web.devのTTFB最適化ガイドでは、TTFBを800ms以内に抑えることを推奨していますが、Cloudflare Workersのエッジ実行では通常50〜100ms程度で応答が完了します。

100点を維持するために

スコアを一度達成することと、維持し続けることは別の課題です。

定期的な自動計測として、PageSpeed Insights APIをCI/CDパイプラインやcronジョブに組み込み、スコアの低下を早期に検知する仕組みが有効です。当サイトのPageSpeed診断ツールでも手動での確認が可能です。

CrUXデータの継続監視として、Search Consoleの「ウェブに関する主な指標」レポートを定期的に確認してください。ラボスコアが100点でも、実ユーザーのデバイスやネットワーク環境によってはフィールドデータが異なる場合があります。特にINPはユーザーの実際の操作パターンに依存するため、ラボテストだけでは検出できない問題が潜む可能性があります。

新規ページタイプ追加時のチェックとして、新しいテンプレートやコンポーネントを追加する際は、必ずキャッシュを無視したPageSpeedテストを実行してください。特に opacitycolor の使い方はAccessibilityスコアに直結します。

外部リソース追加の慎重な判断も重要です。Google Fontsやアナリティクススクリプトの追加は、Performance 100点を崩す最大の要因になります。Lighthouseのパフォーマンススコアリングによると、TBT(Total Blocking Time)が30%、LCP(Largest Contentful Paint)が25%の重みを持っており、外部スクリプトの追加はこの両指標に直結します。導入前に影響を定量評価してください。

まとめ

今回の改善で対処した問題は、いずれも「動くし見える、でもLighthouseが減点する」類のものでした。<main> タグがなくてもページは表示されますし、アンダーラインがなくてもリンクは機能します。しかし、これらの基準を満たすことは、スクリーンリーダーのユーザーや色覚特性を持つユーザーにとって、サイトが使えるかどうかの分かれ目です。

PageSpeed Insightsの100点は目標ではなく基準線です。全項目100点を全ページタイプで達成した今、次に取り組むべきは実ユーザーデータ(CrUX)での検証と、コンテンツ追加に伴うスコア維持の仕組みづくりです。

参考リンク