
t0mmy
学習履歴詳細
実践Next.js 10章 パフォーマンスとキャッシュ 読了
やったこと
- 実践Next.js 10章 パフォーマンスとキャッシュ 読了
学んだこと
並列化でパフォーマンス改善
依存関係にないデータの取得は、 並行でデータを取得することで、パフォーマンスを改善できる。
◆直列取得
const data1 = await getData1(); const data2 = await getData2(); const data3 = await getData3();
◆Promise.all による並列取得
const [data1,data2,data3] = await Promise.all([ getData1(), getData2(), getData3() ])
◆子サーバーコンポーネントに分割して並列取得
import { data1_component } from "..." import { data2_component } from "..." import { data3_component } from "..." ... return ( <> <data1_component /> <data2_component /> <data3_component /> </> )
データ取得処理用に 子 Server Component を用意し、データ取得処理を委譲する。
コンポーネント構造でも、データを並列取得するため、パフォーマンス改善が見込める。
- 可読性向上や再利用性向上といったメリットもある
Server Component の動作に必要なデータが依存関係にない場合、子 Server Component に分割すると良い。
fetchCache の設定
fetch 関数のキャッシュデフォルト設定は auto
。
これは、「動的関数が使用された」ことを起因に、キャッシュの挙動を切り替えるというもの。
fetchするデータと無関係な動的関数が使用された場合でも、動的データ取得扱いされ、リクエストが発生する恐れがある。
- 無関係な動的関数の例 : ログインしているユーザーの情報を取得するための
getServerSession
関数
この場合、 fetch 関数の キャッシュ設定を force-cache
とすると、常に静的データ取得扱いされ、常にキャッシュされるようになる。
静的 Route を増やす実装
無関係な動的関数の使用、静的Route であってほしい Route が、意図せず動的 Route になってしまうことがある。
- ログインしているユーザーの情報を取得するための
getServerSession
関数など - Page のほかに、
Layout.tsx
で動的関数を使用している場合でも、動的 Route 扱いされる
動的 Route の原因となっている、 動的関数を取り除くために、以下の方法が採用できる。
- 動的関数を使用する部分を Client Component として 別コンポーネントに切り出す
動的関数を含む処理を CSR にて処理するよう修正することで、Route が 静的 Route を増やし、パフォーマンス改善につなげることが出来る。
ただし、動的データ取得処理が多い場合、 Server Component でまとめて取得する 動的 Route の方が、結果的に高パフォーマンスに繋がることもある。
動的関数 in CSR + 静的Route が、常に最適解というわけではない。
SSG Route の実装
予め Route のレンダリング結果を出力する SSG (またはISR)は、パフォーマンス改善において効果の高い手法。
SSG Route を提供する
generateStaticParams
関数を使用する。
- SSGしたいパスのパスパラメータを定義する関数
generateStaticParams
関数を export することで、 Next.js に認識される- ビルド時にしか実行されない
例)
export function generateStaticParams() { return [{ id: '1' }, { id: '2' }, { id: '3' }] } // Three versions of this page will be statically generated // using the `params` returned by `generateStaticParams` // - /product/1 // - /product/2 // - /product/3 export default function Page({ params }: { params: { id: string } }) { const { id } = params // ... }
/product/1
、 /product/2
、 /product/3
は、SSGにより、ビルド時に事前レンダリングされる。
他のパラメータ( /product/4
, /product/5
など)は、リクエストが来たらレンダリングされ、レスポンスを返すと同時にSSGが行われる。
generateStaticParams
関数は、非同期として定義することもできる。
非同期関数として定義し、DBから SSGしたいID情報を取得し、return に設定する ... といった使い方が想定される。
generateStaticParams
に複数の値を持たせたい場合、パラメータ部分を配列にするだけ。
export function generateStaticParams() { return [{ slug: ['a', '1'] }, { slug: ['b', '2'] }, { slug: ['c', '3'] }] }
パスパラメータとなる変数が複数存在する場合、その数だけ key-value を定義する
export function generateStaticParams() { return [ { category: 'a', product: '1' }, { category: 'b', product: '2' }, { category: 'c', product: '3' }, ] } // Three versions of this page will be statically generated // using the `params` returned by `generateStaticParams` // - /products/a/1 // - /products/b/2 // - /products/c/3 export default function Page({ params, }: { params: { category: string; product: string } }) { const { category, product } = params // ... }
SSG を考慮したURI設計
パスにクエリパラメータを含め、 searchParams()
を使用する ... というコードだと、 searchParams()
が動的関数のため、動的Route と判断される。
categories/flower?page=1
など
これを避けてSSG Route とした扱いたい場合、クエリパラメータではなくパスパラメータへ変更する...といった改善策がある。
categories/flower/1
など
参考 : https://nextjs.org/docs/app/api-reference/functions/generate-static-params#single-dynamic-segment
Route Segment Config
Route Segment 単位で、レンダリングやキャッシュ設定を一括制御できる機能。
特定の Route Segment に設定すると、 Subtree 全てに、設定内容を反映できる。
以下のように、変数をexport するだけで一括指定出来る。
export const revalidate = false // false | 'force-cache' | 0 | number
SSG Route パフォーマンスの定量評価
以下の評価項目がある
- Time To First Byte (TTFB)
- サーバーからブラウザに、最初のデータが届くまでの時間
- Largetct Contentful Paint (LCP)
- ブラウザの中で、最も大きなコンテンツが表示されるまでの読み込み時間
- Lighthouse スコア
いずれも、開発者ツールで確認できる。
Next.js によるアセットの最適化
画像の最適化
<Image>
コンポーネントの使用
Next.js が提供する <Image>
コンポーネントを使用する。
これは html の <image>
要素を拡張したコンポーネントであり、以下の最適化が自動で適用される。
- 容量の最適化 : 配信に最適な画像形式(webpやavifなど)へ変換
- 視覚上の安定化 : 画像読み込み時、レイアウトずれの発生を自動防止
- 遅延読み込み : 初期表示ではプレースホルダー を表示し、ビューポートに入った時に読み込みを行う
- プレースホルダー表示ため、
alt
属性が必須となっている(空文字列でもOK。基本は画像を説明する文字列)
- プレースホルダー表示ため、
- 画像サイズ変更 : 画像サイズをオンデマンドで変更する
更に最適化された画像ファイルは、Next.js によってキャッシュされる。
- キャッシュの格納先は
.next/cache/images
フォルダ
画像サイズ指定
画像サイズを指定することにより、 「Layout Shift」 というパフォーマンス問題を回避できる。
◆Layout Shift
画像読み込みによって、他のHTML要素を動かしてしまう事象。
画像表示に必要なスペースを、事前に設定しておくことで回避できる。
画像サイズの設定必須。
優先読み込みを指定できる priority props
priority
を追加すると、追加された<Image>
の画像は、優先的に読み込むことが出来る。
これにより、LCP向上につなげることが出来る。
画像の読み込み先を制限する
「信頼できる画像読み込み先」を指定し、信頼できない画像読み込み先との通信をブロックする。
これは、next.config
にて設定する。
{ const nextConfig = { images: { remotePatterns: [ { protocol: "https", hostname: "lh3.googleusercontent.com", }, { protocol: "http", hostname: "127.0.0.1", }, ], }, }; export default nextConfig; }
画像フォーマット指定
next.config
にて、画像形式を指定できる。
const nextConfig = { images: { formats:['image/avif','image/webp'] } }
未指定の場合、 image/webp
が採用される。
画像のキャッシュ時間を指定する
最適化された画像は Next.js によってキャッシュされる。
このキャッシュ時間を、 next.config
にて指定できる。
const nextConfig = { images: { minimumCacheTTL: 60 } }
画像キャッシュを無効化することはできない。
キャッシュ保持期間を可能な限り短くすることで、暫定対応する。
キャッシュ時間は、上記 minimumCacheTTL
の値と、サーバーレスポンスの max-age
Cache Control ヘッダの内、大きい値が採用される。
フォントの最適化
next/font
を使用すると、あらゆる Font ファイルのセルフホスティングが可能。
従来のようにFontファイル とCSSファイルの準備が不要になる。
サブセットの指定
Google Font は、自動的にサブセット化できる。
◆サブセット化
使用している文字だけ抜き出しフォント化すること
サブセット化により、Fontファイルのサイズを削減し、パフォーマンス向上が見込める。
const inter = Inter ({subsets: ["latin"])
CSS Variables の適用
外部CSS を読み込み、一部オプションを追加して使いたい場合は、CSS Variables を使用する。
import { Inter } from "next/font/google"; import styles from "../styles/component.module.css"; const inter = Inter({ variable: "--font-inter", });
スタイルを設定するテキストの「親コンテナのclassName」 をフォントローダーの変数値に設定する。
そして、テキストのclassNameを外部CSSファイルのセレクターが適用されるように設定する。
<main className={inter.variable}> <p className={styles.text}>Hello World</p> </main>
そして、外部CSSファイル(component.module.css)で、以下のようにCSS Variables を使用する。
.text { font-family: var(--font-inter); font-weight: 200; font-style: italic; }
フォント定義ファイルの使用
フォントファイルは、ロードする度にインスタンス生成処理が走る。
何度もインスタンス生成処理が走るのは非効率。
そのため、フォントファイルのロード処理は一か所にまとめておき、 exportする。
他のコンポーネントは、importするだけにしておく。
サードパーティスクリプト読み込みの最適化
Layout Script
複数のRoute でサードパーティスクリプトを読み込む場合は、
next/script
をインポートし、 <Scriptl>
コンポーネントを Layoutコンポーネントに含めるよう定義する。
これにより、 Layout側で一度スクリプトをダウンロードするだけで、 Subtree でもスクリプトを使用できる。
Root Layout に含めることで、全ての Route で使用するスクリプトを定義する事もできる。
不必要なスクリプトのダウンロードは、パフォーマンスの悪化につながる。
基本的に、サードパーティスクリプトのダウンロードは、スクリプトが必要な特定のページまたはサブコンポーネントで行う。
全ページで必要なサードパーティスクリプトのみ、 Root Layout でダウンロードする。
Next.js の 4種類のキャッシュ
Next.js は、レンダリング結果とデータ取得結果をキャッシュする。
以下の順番でキャッシュする。
- Request のメモ化
- Data キャッシュ
- Full Route キャッシュ
- Router キャッシュ
Request のメモ化
fetch 関数の戻り値(取得データ)を一時的にキャッシュする。
サーバー上で重複して呼び出された fetch 関数は、キャッシュした戻り値を再利用する。
同一のfetch関数リクエスト排除を目的としており、これを「メモ化」と呼ぶ。
これは、fetch 関数を使用して同一のリクエストを試みても、リクエストは1回のみに抑えることを意味する。
これにより、コンポーネント単位でデータ取得関数を呼びだしても、キャッシュが機能するようになる。
このメモ化は、レンダリング中のみ有効。
- 言い換えると、レンダリングが完了すると、メモ化したデータはリセットされる
fetch 関数以外で Request をメモ化したい場合は、 React の cache
関数を使用する。
Data キャッシュ
ビルド時、fetch 関数の取得データを永続的にキャッシュする。
当然、変更のない "静的データ" がキャッシュの対象となる。
キャッシュの有効期限は、 Time-based Revalidation の設定で設けることが出来る。
また、On-demand Revalidation にて、能動的に無効化する事もできる。
force-cache
されたデータは、再ビルド時でもキャッシュは削除されず、ずっと再利用され続ける。
ローカル開発だと、Dataキャッシュを直接削除する( .next/cache/fetch-cache
配下)ことで、キャッシュを削除できる。
以下のように設定することで、プロジェクトを通して「キャッシュを使用しない」ことを設定できる。
// Segment Subtree 配下のデータ取得を、全て動的へ export const dynamic = "force-dynamic";
// Segment Subtree 配下のデータ取得時、一切キャッシュしない export const revalidate = 0;
fetch 関数以外で Data キャッシュを作成したい場合、Next.js の unstable_cachce
関数を使用する。
第一引数に渡した非同期関数の戻り値が、キャッシュされるようになる。
Full Route キャッシュ
各 Route のレスポンスを永続的にキャッシュする。
Full Route キャッシュが存在せず、Routeでデータ取得がある場合、「Data キャッシュ」を経由してDataソースにアクセスする。
静的サイト生成(SSG)や静的レンダリングファイルのこと。
キャッシュはビルド時、またはリクエスト時に作成される。
再ビルドするまで、ずっと残る。
- 言い換えると、Full Route キャッシュを削除するには、再ビルドが必要
ただし、 Dataキャッシュの Revalidate が発生すると、関連する Full Route キャッシュも無効になる。
無効になった Full Route キャッシュは、 Dataキャッシュが作成されるときに、同時に作成される。
Router キャッシュ
ブラウザにて保持するキャッシュ。
主な目的は、「戻る」「進む」ボタン処理を高速にしつつ、データアクセスの頻度を削減すること。
一定期間が過ぎると、部分的なRouter キャッシュは自動で無効になる。
キャッシュの有効期間は以下の通り。
- 動的レンダリングRoute : 30s
<Link>
コンポーネントの props にprefetch={true}
を使用すると、有効期間が5分に延長される
- 静的レンダリングRoute : 300s (5分)
<Link>
コンポーネントの props にprefetch={false}
を使用すると、有効期間が30秒に短縮される
この有効期間は切り替え可能。
能動的にキャッシュを無効化するには、以下のいずれかの手段を取る。
- ブラウザで
router.refresh
を実行する- http リクエストが二往復必要になる
- Server Action 内で
coolies.set
またはcookies.delete
を使用する- 古い Full Route キャッシュやDataキャッシュにヒットし、意図しない結果につながる可能性がある
- Server Action 内で on-demand revalidation を実施する
- 推奨
2024年06月02日(日)
3.0時間