DevToolsのLighthouseタブで、緑色の「100」が4つ並ぶ。 これはエンジニアにとって、ある種の中毒性を持つ光景です。
以前の記事でAstroへの移行とLighthouse 100点達成について書きましたが、記事数が増えるにつれて新たな課題が見え隠れし始めました。 今回は、ページネーションの実装によるスケーラビリティの確保と、LCP を極限まで縮めるための試行錯誤、そして「最適化のやりすぎ」で開発環境を破壊しかけた失敗談を共有します。
課題1:全件取得のボトルネックとページネーション
これまでのトップページ(index.astro)は、シンプルに「全記事を取得して表示」していました。
しかし、記事が増えれば増えるほど、ビルド時の画像処理やクライアントでのDOM量が増加します。Astroのアイランドアーキテクチャは優秀ですが、不要なデータを送らないに越したことはありません。
解決策:[...page].astro への移行
Astroには強力なページネーション機能が標準で備わっています。これを使うために、ファイル名を index.astro から [...page].astro に変更し、getStaticPaths で paginate 関数を使用するように書き換えました。
// src/pages/[...page].astro
export async function getStaticPaths({ paginate }) {
const posts = (await getCollection('blog')).sort(/* ... */);
// 1ページあたり10件に制限
return paginate(posts, { pageSize: 10 });
}
const { page } = Astro.props;
これだけで、page.data に当ページ分の記事だけが格納され、page.url.prev / page.url.next で前後のページへのリンクも自動生成されます。
開発者体験として、この手軽さは驚異的です。
課題2:LCPスコアの低下と「レイジー」の弊害
ページネーションでDOM量は減りましたが、Lighthouseのパフォーマンススコアにおいて LCP が警告を出していました。
原因は、すべての画像に対して「善意で」付与していた loading="lazy" でした。
ファーストビューに入る一番上の記事(ヒーロー画像)まで遅延読み込みされてしまい、ブラウザが「あ、これも読み込むのね」と気づくのが遅れていたのです。
解決策:条件付きの Eager Loading
トップページのリストにおいて、「1枚目の画像だけは即座に読み込み、2枚目以降は遅延させる」 というロジックを実装しました。
// 1枚目(index === 0)だけ eager / high priority に設定
<PostCard
post={post}
loading={index === 0 ? "eager" : "lazy"}
fetchpriority={index === 0 ? "high" : "auto"}
/>
また、個別記事ページ(blog/[...slug].astro)のヒーロー画像においても、ネイティブの <img> タグではなく Astroの <Image /> コンポーネントを使用しつつ、loading="eager" と fetchpriority="high" を明示しました。
失敗談:最適化の「やりすぎ」によるTTFB悪化
LCPをさらに短縮しようとして、「サーバーサイドで getImage を使って画像のURLを計算し、<head> タグ内で <link rel="preload"> する」 という実装も試みました。
理論上は最速のはずですが、これが開発環境(pnpm run dev)で牙を剥きました。
各リクエストのたびに同期的に画像処理が走ってしまい、TTFBが12秒近くまで悪化してしまったのです。さらに、Astroのコンパイラがクラッシュする事態にも見舞われました。
教訓:
Astroの <Image /> コンポーネントが出力する最適化だけで十分強力です。開発サーバーの挙動をブロックしてまで、手動でプリロードをねじ込む必要はありませんでした。SSG(静的サイト生成)ならではの落とし穴といえます。
また、そもそもパフォーマンス計測を pnpm run dev(開発サーバー)で行っていたのが混乱の原因でした。開発サーバーは最適化されていないため、本番相当の計測をする際は必ず pnpm run build && pnpm run preview で確認するべきですね。基本を忘れてはいけません。
仕上げ:アクセシビリティの階層構造修正
最後に、Accessibilityスコアにおける「Heading levels should only skip by one(見出しレベルは1つずつ下がるべき)」という指摘を修正しました。
h1: サイトタイトルh3: 記事タイトル(以前の状態)
となっており、h2 が欠落していました。これを以下のように修正しました。
h1: サイトタイトルh2: 記事タイトル(記事一覧における各カードのタイトル)
また、もう一点アクセシビリティで指摘を受けたのが、コードハイライトのコントラスト比です。
私は好んで「Dracula」テーマを使っていますが、デフォルトのコメント色(#6272A4)は背景色とのコントラストが低く、アクセシビリティ監査に引っかかります。
ここでテーマごと変更するのは簡単ですが、こだわりは捨てたくありませんでした。そこで、以下のCSSハックを追加し、「コメント部分だけ強制的にコントラスト比の高い色(Cool Gray 400)にする」という対策を行いました。
/* Accessibility Hack: Force higher contrast for comments in Dracula theme */
pre.astro-code span[style*="color:#6272A4"] {
color: #A0AEC0 !important; /* Cool Gray 400 */
}
些細なことですが、スクリーンリーダー利用者にとっても、SEOにとっても、「正しいHTML」を書くことはエンジニアの規律です。
結果:再びの満点
これらの修正を経て、ビルド環境でのLighthouseスコアは再び オールグリーン、満点 を達成しました。
- Performance: 100
- Accessibility: 100
- Best Practices: 100
- SEO: 100
単に数字を追うだけでなく、読み手にとっても、書き手にとっても快適なブログ環境が整いました。 AIエージェントと共にコードを書くことで、こうした細かい最適化のサイクルを高速に回せるのは、現代のエンジニアリングの醍醐味と言えるでしょう。