【パフォーマンス改善】LCP改善のケーススタディ
実際のサイト・コードをベースにLCPの改善を行います。本記事を読むことでLCPを改善する手順を理解できるようになります。


はじめに
私が個人開発しているレシピサイトを対象にLCP改善を実施します。本記事をケーススタディとして、LCP改善のステップを理解することができます。
本ケーススタディで対象にするサイト:https://n-recipes.com/
結論
今回のケースでは以下の2つが大きな原因でした。
- フォントファイルの読み込み
- 画像のファイルの読み込み
以下の対処により、LCPを改善することができました。
- カスタムフォントではなく、tailwindのフォントを使用
- 画像をCDN経由で取得するように変更
前提
レシピサイトは、Next.js/microCMSを使ったWebサイトになっています。Next.jsアプリケーションはGoogle CloudのCloud Runでホストしています。
より詳しいアーキテクチャはこちらにまとめています。(本記事を読み進める上では、上記の前提だけ把握しておけば問題はありません。)
現状把握
1. Lighthouseでパフォーマンス確認
まずは現状を把握します。Chrome DevtoolsのLighthouseでパフォーマンスを確認します。LCPが悪いことが確認できます。(FCPとSpeed Indexも気になりますが、本記事ではLCPにフォーカスします。)

2. ボトルネックの把握
次に詳しい診断を確認します。「最大コンテンツの描画」要素でLCP要素と、どのサブパートにどれくらい時間がかかかっているかの内訳を確認します。今回のケースではレンダリング遅延が長いことが確認できます。まずはレンダリング遅延の短縮を目指します。

原因分析
あたりをつける
レンダリング遅延は、LCPリソースの読み込みが完了してから、LCP要素がレンダリングされるまでの時間です。(図の#4)それぞれのサブパートの解説はこちらをご覧ください。「LCPの4つのサブパート図」をみるとスタイルシートの読み込みに時間がかかっている可能性が高いことがわかります。

また、こちらによるとスタイルシートの読み込みが終わってない可能性もあります。

そのため、今回のケースではスタイルシート・スクリプトの読み込みに時間がかかっているとあたりをつけます。
どのスタイルシート・スクリプトの読み込みに時間がかかっているかを調べる
- ボトルネックの確認
Devtoolsのパフォーマンスタブから、どのスタイルシート・スクリプトがボトルネックになっているかを確認します。

LCP要素は左側の赤枠で囲った扉の画像です。この画像の読み込みを表しているのが、緑色のバーです。この緑色のバーの読み込みが終わっても、2つのスタイルシート(紫色のバー)の読み込みが終わっておらず、この差分がレンダリング遅延になっていることがわかります。
- スタイルシートの詳細の確認
続いてスタイルシートの内容を確認すると、font-faceの定義をしているスタイルシートであることがわかります。


以上のことから、フォントの読み込みがボトルネックになっていることがわかりました。
改善
改善前
フォントの読み込みを行なっている該当コードを確認します。Next.jsのGoogleFontを使用し、カスタムフォントの読み込みを行なっています。
import { Shippori_Antique, Shippori_Mincho } from 'next/font/google';
export const shipporiMincho = Shippori_Mincho({
subsets: ['latin'],
weight: ['400', '500'],
preload: false,
display: 'swap',
variable: '--font-shippori-mincho',
});
export const shipporiAntique = Shippori_Antique({
subsets: ['latin'],
weight: ['400'],
preload: false,
display: 'swap',
variable: '--font-shippori-antique',
});
import { shipporiAntique, shipporiMincho } from './font';
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang='ja'>
<body
className={`base ${shipporiMincho.variable} ${shipporiAntique.variable} antialiased bg-white h-screen flex flex-col`}
>
<Header />
{/* <GoogleSignIn /> */}
{children}
<Footer />
</body>
<GoogleAnalytics gaId='G-8XQ426S2XN' />
</html>
);
}
fontFamily: {
mincho: ['var(--font-shippori-mincho)'],
antique: ['var(--font-shippori-antique)'],
},
このレシピサイトはGoogle Cloud Runにデプロイしていることから、Next.jsによるフォント最適化のメリットをあまり得られていません。(VercelのCDNエッジを利用できない)
対処としては以下の2つが考えられます。
- Google FontをCDN経由で読み込む
- Cloud Runのサーバスペックが低いことから、外部CDN経由でフォントを読み込んだ方がパフォーマンス良くなる可能性がある
- ただし、追加のネットワークリクエストが増える分のオーバヘッドと相殺される可能性もある
- Google Font(カスタムフォント)を使わない
- CSSフレームワークとしてTailwindを使っているので、Tailwindに定義されているフォントを使用する
今回はパフォーマンスを優先するため、後者の方針をとり、使用するフォントを変更することにしました。
改善後
font.tsと、tailwind.config.jsのカスタムフォントを定義している部分は削除します。layout.tsxからもfont.tsの定義と、フォントを読み込んでいるクラスを削除します。
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang='ja'>
<body
className={`base antialiased bg-white h-screen flex flex-col`}
>
<Header />
{/* <GoogleSignIn /> */}
{children}
<Footer />
</body>
<GoogleAnalytics gaId='G-8XQ426S2XN' />
</html>
);
}
font-minchoクラスを使っていたコンポーネントは、Tailwindのクラスとして用意されているfont-serifに変更します。
結果
修正後もう一度Lighthouseでパフォーマンスを測定すると、LCPが改善しました。

またパフォーマンスを確認するとスタイルシートの読み込み(紫色のバー)が、LCP要素(緑色のバー)の読み込みよりも前に終わっていることがわかります。(=LCP要素のレンダリングをブロックしていない)

おわりに
今回はフォントファイルの読み込みがボトルネックになっていました。Next.jsをCloud Run(Vercel以外)にデプロイする場合は、フォントの最適化の恩恵が十分得られないことがわかりました。
next/imageタグを使用した画像の配信をしている場所も、同様の問題を抱えている可能性があります。今後はこちらの改善についても、記事をまとめる予定ですので、興味のある方はぜひご覧ください。

通りすがりのラマ🦙
このブログでは、個人開発で得た知見や、興味のあるテクノロジー、時々デザインに関する記事を執筆します。 日々公開されている情報に助けられているので、自分が得た知見も世の中に還元していければと思います。 解決できないバグに出会うと、つばを飛ばします。🦙 経歴:情報工学部→日系SIer→外資系IT企業 興味:Webアプリケーション開発、Webデザイン 趣味:個人開発、テニス