Next.js 13 レンダリングとデータフェッチ
テーマ選定理由
昨年10月末にNext.js13が発表され、様々な変更点の中で、レンダリングやデータフェッチの方式がReact18の恩恵が受けられるように変わり、パフォーマンスが向上するということを知り、これまで新規開発の案件しか経験していなかった自分にとって、普段の業務でも特別扱ったことのなかった非機能要件(+Next.js13)について勉強できるよい機会だと考え選定した。
概要
- レンダリング
- サーバーコンポーネント、クライアントコンポーネント
- 静的、動的レンダリング
- データフェッチ
- 奨励事項
- SCでのデータフェッチ
- 静的、動的データフェッチ
- パフォーマンス検証
- SC、CCのフェッチデータ表示速度
-
fetch()
リクエストの自動重複排除 - ストリーミング
- 結論&感想
レンダリング
サーバーコンポーネント(SC)、クライアントコンポーネント(CC)
Next.js 13からの新しい考え方として、サーバーコンポーネント(SC)、クライアントコンポーネント(CC) というものがある。
SC、CCとは?
- 「サーバーサイドでレンダリングを完結させる部分とクライアントサイドでレンダリングを完結させる部分をコンポーネントレベルで分けましょう!」という方針
- 従来のgetServerSideProps等のサーバー側、SWR等のクライアント側からのデータフェッチをpage側で行っていたものを、コンポーネントで行おう!という試み
- レンダリング、ハイドレーションをコンポーネントごとに行うことができるため、完了した部分から表示することができる(後述Suspence)
- SC, CCの使用で、サーバー、クライアントどちらで完結させるかを簡単に設定できる。
-
app
ディレクトリではデフォルトでサーバーコンポーネントになる。- サーバーサイドでレンダリングしたほうが初期表示までが速いため。(クライアントに送信されるJSの量を減らすことができるため。)
SC vs CC
極力SCの使用が奨励されている。
表示が早いから。
windowオブジェクトにアクセスする必要がある場合や、useEffect,useState等を使用する場合などのインタラクティブなロジックがある時にCCとして切り出す。
表示形のコンポーネントはSC、操作形のコンポーネントはCCのイメージ。
サーバーでの静的(Static)、動的(Dynamic)レンダリング
静的レンダリング
- 静的レンダリングの使用でビルド時にSC、CCの両方をサーバー上で事前にレンダリングできる。
- 作業結果はキャッシュされ再利用される
- 再検証(revalidate)も可能
v12までの静的サイト生成(SSG)、増分性的再生性(ISR)に相当(getStaticProps)
動的レンダリング
- SCとCC両方が要求時にサーバー上でレンダリングされる
- キャッシュはされない
v12までのSSRに相当(getServerSideProps)
レンダリングプロセス
SCとCCとでレンダリングのプロセスが異なる。
CC
- HTMLとJSONが事前にレンダリングされ、静的レンダリングの場合はサーバーにキャッシュされる。キャッシュの結果はハイドレーションのためにクライアントに送られる。
SC
- Reactによってサーバー上でレンダリングされ、そのペイロードがHTMLの生成に使用される。また、そのペイロードはクライアント上のコンポーネントをハイドレートするためにも使用されるため、クライアントにJavaScriptを送る必要がない。
コンポーネントツリー
上記の特性を踏まえた上で、実際にページに表示する要素としては以下表のようになる。
[引用]: https://beta.nextjs.org/docs/rendering/fundamentals#component-level-client-and-server-rendering
組み合わせの方法としては
- CCをSCにインポートする
- SCを
children
またはprops
としてCCに渡す
データフェッチ
奨励事項
- SC内でデータフェッチ
- データを並行してフェッチして時間短縮
- データの利用箇所でデータをフェッチ
- Loading UI, Streaming, Suspenseを使用してページを段階的にレンダリングし、コンテンツが読み込み中に結果を示す。
getServerSideProps, getStaticProps, getInitialProps はappディレクトリではサポートされない。
SCでのデータフェッチ
WEB APIのfetch()
を使用することが推奨されている。
fetch()
はReact, Nextで拡張されており、再検証やキャッシュについてなどのオプション指定が可能。
const fetchUsers: () => Promise<User[]> = async () => {
const res = await fetch("http://localhost:3000/api/users", { next: { revalidate: 0 } });
return res.json();
};
const Users = async () => {
const users = await fetchUsers();
return (
<>
...
</>
);
};
リクエストの自動重複排除
データの利用箇所でフェッチすることが奨励されている
つまり複数コンポーネントで同一のapiを呼び出すことになりパフォーマンスが落ちない?
→ コンポーネントツリー内の同一入力によるフェッチはキャッシュされて自動で重複を排除してくれるので大丈夫。
引用: https://beta.nextjs.org/docs/data-fetching/fundamentals#automatic-fetch-request-deduping
静的、動的データフェッチ
- 従来のgetStaticProps、 getServerSidePropsで静的、または動的データをフェッチしていたが、Next.js13ではfetch()APIのオプションで管理するようになった。
- 違いはキャッシュを残すかどうか。
- オプションでnext.revalidateを使用してリソースの有効期限を指定することも可能。
// 静的(デフォルト)
fetch("http://localhost:3000/api/users");
// 動的
fetch("http://localhost:3000/api/users", { cache: 'no-store' });
// { next: { revalidate: 0 } } としても動的になる。
ストリーミング
Next.js13のコンポーネント構造(SC, CC)の恩恵として、各コンポーネントをチャンク(まとまり的な解釈で良い?)としてみなすことができるため、Suspense
を利用してデータに依存しないコンポーネントを早く、データに依存するコンポーネントを段階的に表示することができる。
const Page = async () => {
return (
<div style={{ display: "flex" }}>
<Suspense fallback={<div>Loading users...</div>}>
<Users />
</Suspense>
<Suspense fallback={<div>Loading posts...</div>}>
<Posts />
</Suspense>
</div>
);
};
パフォーマンス検証
以下の点について検証した。
- SC、CCのフェッチデータ表示速度
-
fetch()
リクエストの自動重複排除- 動的フェッチにしても重複は排除されるのか
- ストリーミング
Git Hubリポジトリ: https://github.com/YoshidaTakeshi/nextjs13-rendering-datafetch
結論&感想
- レンダリングとデータフェッチの方式が変わり、コンテンツをより高速かつ段階的に表示することが可能となった。
- SC、CCの登場によりどのコンポーネント(データ)を静的(動的)にレンダリング(フェッチ)するのかより厳密に考慮必要があると考えられる。
- デフォルトで静的レンダリングとなってしまうことの意図は理解できたが、アプリケーションでのデータの整合性を担保するためには動的にレンダリングした方が良いものの方がおおそう(?)
- どれくらいのデータ量に対するSC,CCそれぞれの表示速度がどのくらいか、また、静的レンダリングと動的レンダリングとではどれくらいの差があるのかについても定量的に検証したかった。
参考
- Next.js公式ドキュメント: https://beta.nextjs.org/docs/getting-started
Discussion