モダンNext.jsにおける構成、キャッシュ、アーキテクチャ

VVercel
Computing/Software

Transcript

00:00:00(アップビートな音楽) - 皆さん、こんにちは。
00:00:06私の名前はオーロラです。
00:00:07ノルウェー出身のウェブ開発者です。
00:00:09Crane Consultingでコンサルタントとして働いています。現在のコンサルティングプロジェクトでは、
00:00:13Next.jsのApp Routerを使って積極的に開発を進めています。
00:00:16今日は、
00:00:17最新のNext.jsにおけるコンポジション、
00:00:19キャッシング、
00:00:19アーキテクチャに関するパターンをご紹介します。これにより、
00:00:22スケーラビリティとパフォーマンスを確保できるようになります。
00:00:24まず、このトークの最も基本的な概念である「静的レンダリング」と「動的レンダリング」についておさらいしましょう。
00:00:30Next.jsのApp Routerでは、この両方に出会います。
00:00:33静的レンダリングは、
00:00:34事前レンダリングされたコンテンツをキャッシュしてグローバルに配信できるため、
00:00:38ユーザーがより速くアクセスできるようになり、
00:00:40高速なウェブサイト構築を可能にします。
00:00:42例えば、Next.js Confのウェブサイトです。
00:00:46静的レンダリングは、ユーザーのリクエストごとにコンテンツを生成する必要がないため、サーバーの負荷を軽減します。
00:00:51また、
00:00:52事前レンダリングされたコンテンツは、
00:00:53ページ読み込み時にすでに利用可能であるため、
00:00:55検索エンジンのクローラーがインデックスを作成しやすくなります。
00:00:58一方、動的レンダリングは、アプリケーションがリアルタイムまたは頻繁に更新されるデータを表示することを可能にします。
00:01:05また、ダッシュボードやユーザープロフィールのようなパーソナライズされたコンテンツを提供することも可能です。
00:01:09例えば、Vercelのダッシュボードです。
00:01:12動的レンダリングでは、リクエスト時にしか知り得ない情報にアクセスできます。
00:01:16この場合、どのユーザーがダッシュボードにアクセスしているか、つまり私です。
00:01:20ページを動的にレンダリングさせる特定のAPIがあります。
00:01:25ページに渡される`params`や`search params`プロパティ、
00:01:28またはそれらに相当するフックを使用すると、
00:01:30動的レンダリングが発生します。
00:01:32ただし、
00:01:32`params`を使用すると、
00:01:33汎用的な`static params`を使って事前レンダリングされるページのセットを事前に定義できます。また、
00:01:38ユーザーによって生成されるページをキャッシュすることも可能です。
00:01:40さらに、受信リクエストのクッキーやヘッダーを読み取ると、ページは動的レンダリングにオプトインされます。
00:01:46ただし、
00:01:47`params`とは異なり、
00:01:48ヘッダーやクッキーを使って何かをキャッシュしたり事前レンダリングしようとすると、
00:01:51ビルド時にエラーが発生します。その情報は事前に知ることができないためです。
00:01:56最後に、
00:01:56`fetch`を`data cache configuration no store`と共に使用すると、
00:02:00動的レンダリングが強制されます。
00:02:00これらはいくつかある動的レンダリングを引き起こすAPIの一部ですが、私たちが最もよく遭遇するものです。
00:02:06以前のNext.jsのバージョンでは、ページは完全に静的か、完全に動的かのいずれかでレンダリングされていました。
00:02:13ページ上の1つの動的APIが、ページ全体を動的レンダリングにオプトインさせていました。
00:02:17例えば、クッキーの値に対して簡単な認証チェックを行う場合などです。
00:02:20ReactサーバーコンポーネントとSuspenseを利用することで、
00:02:23パーソナライズされたウェルカムバナーやおすすめのような動的コンテンツを、
00:02:27準備ができた時点でストリーミングできます。ニュースレターのような静的コンテンツを表示しながら、
00:02:31Suspenseでフォールバックのみを提供します。
00:02:34しかし、
00:02:34動的なページにフィーチャープロダクトのような複数の非同期コンポーネントを追加すると、
00:02:40それらが動的APIに依存していなくても、
00:02:42リクエスト時に実行されてしまいます。
00:02:45そのため、
00:02:45最初のページ読み込みをブロックしないように、
00:02:48それらのコンポーネントもSuspenseでストリーミングし、
00:02:51余分な作業としてスケルトンを作成したり、
00:02:53累積レイアウトシフトのようなことを心配したりしていました。
00:02:56しかし、ページはしばしば静的コンテンツと動的コンテンツの混在です。
00:03:01例えば、ユーザー情報に依存しつつも、ほとんどが静的データで構成されているEコマースアプリなどです。
00:03:07静的か動的か、
00:03:08どちらかを選ばざるを得ない状況では、
00:03:10決して、
00:03:11あるいはめったに変わらないコンテンツに対してサーバーで多くの冗長な処理が発生し、
00:03:16パフォーマンスにとって最適ではありませんでした。
00:03:19この問題を解決するため、昨年のNext.js Confで`use cache`ディレクティブが発表されました。
00:03:26そして今年、基調講演で見たように、Next.js 16で利用可能になりました。
00:03:30`use cache`を使えば、ページはもはや静的レンダリングか動的レンダリングのどちらかに強制されることはありません。
00:03:36両方になり得ます。
00:03:37Next.jsは、`params`のようなものにアクセスするかどうかでページが何であるかを推測する必要がなくなりました。
00:03:43すべてがデフォルトで動的であり、`use cache`を使うことで明示的にキャッシングにオプトインできます。
00:03:47`use cache`は、コンポーザブルなキャッシングを可能にします。
00:03:51ページ、Reactコンポーネント、または関数のいずれかをキャッシュ可能としてマークできます。
00:03:55ここでは、
00:03:56フィーチャープロダクトコンポーネントを実際にキャッシュできます。これはリクエストや処理を必要とせず、
00:04:01動的APIを使用しないためです。
00:04:03これらのキャッシュされたセグメントは、
00:04:05部分的な事前レンダリングによってさらに事前レンダリングされ、
00:04:08静的シェルの一部として含まれることができます。つまり、
00:04:10フィーチャープロダクトはページ読み込み時に利用可能になり、
00:04:13ストリーミングする必要がありません。
00:04:14さて、この重要な背景知識を得たところで、デモを行いましょう。
00:04:19Next.jsアプリでよく遭遇する一般的な問題を抱えるコードベースの改善です。
00:04:24これらには、
00:04:24深いプロップドリリングによる機能の保守やリファクタリングの困難さ、
00:04:28冗長なクライアントサイドJavaScriptと複数の責任を持つ大規模なコンポーネント、
00:04:32そして静的レンダリングの欠如による追加のサーバーコストとパフォーマンスの低下が含まれます。
00:04:36では、始めましょう。
00:04:37少々お待ちください。
00:04:50よし、完璧です。
00:04:54これは非常にシンプルなアプリケーションです。
00:04:56Eコマースプラットフォームにインスパイアされています。
00:04:59では、ここで最初のデモをしましょう。
00:05:01このページを読み込むことができます。
00:05:03このようなフィーチャープロダクトのようなコンテンツがあります。
00:05:06フィーチャーカテゴリや異なる製品データがあります。
00:05:09こちらには「すべて閲覧」ページもあり、プラットフォーム内のすべての製品を見て、ページを切り替えることができます。
00:05:20そして、こちらには「About」ページがあり、これは静的です。
00:05:24ユーザーとしてサインインすることもできます。
00:05:27そうすると、私のユーザーとしてログインされます。
00:05:30そして、こちらのダッシュボードでパーソナライズされたコンテンツも取得できます。
00:05:33例えば、おすすめ商品や、こちらのパーソナライズされた割引などです。
00:05:38ここで注目してほしいのは、かなり良い組み合わせになっていることです。
00:05:42あ、もう一つ、お見せするのを忘れていたページがあります。
00:05:45最も重要な製品ページです。
00:05:47ここでも製品情報を見ることができ、気に入ればユーザーのために保存できます。
00:05:52このアプリでは、
00:05:53ユーザーに依存する機能が多いため、
00:05:55静的コンテンツと動的コンテンツがかなりうまく混在していることに注目してください。
00:05:59コードも見てみましょう。こちらにあります。
00:06:05もちろん、Next.js 16のApp Routerを使っています。
00:06:08Aboutページ、Allページ、製品ページなど、すべての異なるページがあります。
00:06:13また、Appフォルダをきれいに保つために、フィーチャースライシングを使用しています。
00:06:17Prismaを使ってデータベースとやり取りする様々なコンポーネントとクエリがあります。
00:06:23ええ、そして、これらすべてを意図的に遅くしました。
00:06:25だから、何が起こっているかをより簡単に確認できるように、この非常に長い読み込み段階があるのです。
00:06:31このアプリケーションで実際に抱えている、
00:06:33私たちが取り組みたいと考えていた共通の問題は、
00:06:36プロップドリリングによる機能の保守やリファクタリングの困難さ、
00:06:39過剰なクライアントサイドJavaScript、
00:06:42そして静的レンダリングの欠如による追加のサーバーコストとパフォーマンスの低下でした。
00:06:47このデモの目標は、
00:06:48コンポジション、
00:06:49キャッシング、
00:06:50アーキテクチャに関するいくつかのスマートなパターンを使ってこのアプリを改善し、
00:06:55それらの一般的な問題を解決し、
00:06:57より高速でスケーラブル、
00:06:58そして保守しやすいものにすることです。
00:07:01では、始めましょう。
00:07:02最初に修正したい問題は、実はプロップドリリングに関連しています。
00:07:05それは、こちらのページにあります。
00:07:10ここで注目してください。一番上に`loggedIn`変数があります。
00:07:15いくつかのコンポーネントに渡しているのがわかるでしょう。
00:07:17実際には、このパーソナルバナーまで複数レベルにわたって渡されています。
00:07:20これでは、ウェルカムバナーに常に`loggedIn`の依存関係があるため、ここでの再利用が難しくなります。
00:07:28サーバーコンポーネントでは、
00:07:29データフェッチをそれを使用するコンポーネントに押し下げ、
00:07:33ツリーのより深いところでプロミスを解決するのがベストプラクティスです。
00:07:37そして、
00:07:38認証済みとして取得するために、
00:07:39これが`fetch`またはReact Cacheのようなものを使用している限り、
00:07:44この呼び出しを複数回重複させても問題なく、
00:07:46コンポーネント内の好きな場所で再利用できます。
00:07:48ですから、再利用は全く問題ありません。
00:07:51では、これをパーソナライズされたセクションに移動させましょう。
00:07:54そして、このプロップはもう必要ありません。
00:07:57そして、直接ここに...おっと...置きます。
00:08:01そして、これをもう渡す必要はありません。
00:08:04そして、この非同期呼び出しをパーソナライズされたセクションに移動させたので、ページをブロックすることはなくなります。
00:08:09ここでシンプルなSuspenseを使って、これを中断させることができます。
00:08:13そして、このフォールバックは必要ありません。
00:08:16ウェルカムバナーについても、同じことをするつもりです。
00:08:22しかし、ここで`loggedIn`変数や値を取得しようとしても、うまくいきませんよね?
00:08:27これはクライアントコンポーネントだからです。
00:08:29ですから、別の方法で解決する必要があります。
00:08:30そして、これを解決するために、かなりスマートなパターンを使います。
00:08:33実際には、レイアウトに入り、ここにあるすべてを`AuthProvider`でラップします。
00:08:39ですから、これをアプリ全体に配置し、この`loggedIn`変数をこちらで取得します。
00:08:45そして、ルートレイアウト全体をブロックしたくはありません。
00:08:48ここで`await`を削除しましょう。
00:08:50そして、これをプロミスとして`AuthProvider`に渡すだけです。
00:08:55そして、これはそのプロミスを含んでいるだけです。
00:08:57読み込む準備ができるまで、そこで待機しているだけです。
00:09:01これで設定が完了しました。
00:09:03つまり、まずこのプロップを削除できます。
00:09:09そして、パーソナルバナーにドリルダウンしているこれも削除します。
00:09:12そして、ここでのプロップドリリング、つまりシグネチャも削除します。
00:09:16そして、
00:09:16作成した`AuthProvider`を使って、
00:09:19パーソナルバナー内で`useAuth`でこの`loggedIn`値をローカルにフェッチできます。
00:09:26そして`use`で読み取ります。
00:09:28ですから、これは解決中にこれを中断する必要があるような形で機能します。
00:09:33これで、その小さなデータフェッチをパーソナルバナー内に配置しました。
00:09:37そして、これらのプロップをあちこちに渡す必要はありません。
00:09:40そして、これが解決している間、これもフォールバックで中断させましょう。
00:09:44そして、奇妙な累積シフトを避けるために、ここに一般的なバナーを表示しましょう。
00:09:51そして最後に、これも削除します。
00:09:53これで、このウェルカムバナーはコンポーザブルになりました。
00:09:58再利用可能です。
00:09:59ホームページに変なプロップや依存関係はありません。
00:10:02そして、これを非常に簡単に再利用できるので、こちらのブラウザページにも追加しましょう。ここです。
00:10:11そして、何の依存関係もなく、ここで使うことができます。
00:10:15これらのパターンを通じて、
00:10:17React CacheとReact Useを活用して優れたコンポーネントアーキテクチャを維持し、
00:10:25コンポーネントをより使いやすく、
00:10:27コンポーザブルにすることができます。
00:10:30よし。
00:10:31次の一般的な課題に取り組みましょう。それは、
00:10:34過剰なクライアントサイドJavaScriptと、
00:10:37複数の責任を持つ大規模なコンポーネントです。
00:10:40実際、それもこちらの「All」ページにあります。
00:10:43そしてまた、このウェルカムバナーに取り組む必要があります。
00:10:46現在、これはクライアントコンポーネントです。
00:10:48クライアントコンポーネントである理由は、ここにこの非常にシンプルな「dismissed」状態があるからです。
00:10:53これをクリックするだけです。
00:10:54良いUIインタラクションです。
00:10:56それは問題ありません。
00:10:57しかし、
00:10:57問題なのは、
00:10:58そのせいでこのコンポーネント全体をクライアントサイドコンポーネント、
00:11:01つまりクライアントコンポーネントに変換してしまうことです。
00:11:04そして、クライアントサイドでフェッチするためにSWRまで使っています。
00:11:07ここにAPIレイヤーがあります。
00:11:08データに型安全性がなくなりました。
00:11:11ええ、これは必要ありません。
00:11:12そして、UIロジックとデータを絡めているため、関心の分離も破っています。
00:11:18では、これを修正するために別のスマートなパターンを使いましょう。
00:11:21それはドーナツパターンと呼ばれています。
00:11:23基本的に、これをクライアントサイドのラッパーに抽出します。
00:11:27では、ここで新しいコンポーネントを作成しましょう。
00:11:29そして、`BannerContainer`と名付けましょう。
00:11:32そして、これは`use client`ディレクティブを使ってインタラクティブなロジックを含みます。
00:11:37シグネチャを作成できます。
00:11:38先ほど持っていたすべてを貼り付けられます。
00:11:42そして、これらのバナーを使う代わりに、ここに`children`というプロップをスロットします。
00:11:48だから、ドーナツパターンと呼ばれるのです。
00:11:50サーバーレンダリングされたコンテンツの周りに、
00:11:52このラッパーUIロジックを作成しているだけです。あるいは、
00:11:54サーバーレンダリングされたコンテンツである可能性があります。
00:11:56そして、このクライアントサイドの依存関係がなくなったので、`use client`を削除できます。
00:12:01代わりに、ここで`isAuth`非同期関数を使用できます。
00:12:06これを非同期サーバーコンポーネントにできます。
00:12:09クライアントサイドのフェッチをサーバーサイドのフェッチに置き換えることさえできます。
00:12:11では、ここで直接割引データを取得しましょう。
00:12:16割引データ。
00:12:18そして、以前のように型安全性を保ちながら、通常のメンタルモデルを利用するだけです。
00:12:24つまり、いずれにせよ使いたくないこのAPIレイヤーも削除できます。
00:12:29最後に、
00:12:29`isLoading`については、
00:12:31サーバーレンダリングされたコンテンツを含むドーナツパターンバナーコンテナで、
00:12:35新しいウェルカムバナーをエクスポートするだけです。
00:12:38つまり、この`isLoading`はもう必要ありません。
00:12:40基本的に、この全体をサーバーコンポーネントにリファクタリングし、UIロジックのポイントを抽出しました。
00:12:46しかし、これは何でしょうか?
00:12:48またエラーが出ているようです。
00:12:51これは実はMotionが原因です。
00:12:53Motionを使っています。
00:12:54素晴らしいアニメーションライブラリですが、`useClient`ディレクティブが必要です。
00:12:59そしてまた、アニメーションのためだけにこれを`useClient`にする必要はありません。
00:13:03再びドーナツパターンラッパーを作成し、これらのアニメーションのラッパーを抽出するだけです。
00:13:10つまり、ここで何もクライアントサイドに変換する必要はありません。
00:13:14そして、おそらくここで何か見落としているでしょう。
00:13:17ええ。
00:13:18ほらね。
00:13:21これで、ここにあるすべてがサーバーに変換されました。
00:13:23同じインタラクションがあります。
00:13:24ここにはまだインタラクティブなロジックがありますが、これでデータをフェッチする一つの方法ができました。
00:13:29そして、クライアントサイドJSが大幅に減りました。
00:13:31実際、私はこのUI境界ヘルパーにこのドーナツパターンを自分で使っています。こんな感じです。
00:13:42見えますか?
00:13:43これで、私が何を言いたいのか、またわかりますよね?
00:13:45ドーナツパターンでは、サーバーコンポーネントの周りにこのクライアントコンポーネントがあります。
00:13:49他の多くのコンポーネントも、このUIヘルパーでマークしました。
00:13:53ここにも、さらにサーバーコンポーネントがあります。
00:13:56もうかなり慣れてきたので、これらも改善しましょう。
00:14:01それらはフッターにあります。
00:14:04これらのカテゴリ...つまり、独自のデータをフェッチするこの素晴らしいコンポーネントがあります。
00:14:08そして、もし非常に長くなった場合に備えて、この`showMore`機能を追加したかったのです。
00:14:14そして、ドーナツパターンを使えば、ここに`ShowMore`コンポーネントをラップするだけです。
00:14:20そして、これが私のUIロジックを含みます。
00:14:23そして、こんな感じに見えますよね?
00:14:27かなりクールです。
00:14:28そして、これがクライアントロジックを含み、状態を使用できるようになります。
00:14:33`children`の数と配列を使ってこれをスライスしています。
00:14:36そして、
00:14:37ここで素晴らしいのは、
00:14:37これら2つが完全にコンポーザブルで再利用可能なコンポーネントとなり、
00:14:40このように連携して機能することです。
00:14:42これが、ここで学んでいるこれらのパターンの真の美しさです。
00:14:45これはどんなことにも使えます。
00:14:50こちらのモーダルにも使っています。
00:14:52ええ、次にサーバーコンポーネントに何らかのクライアントロジックを追加することを検討する際には、これを思い出してください。
00:14:59さて、ドーナツパターンはわかりました。
00:15:01これを使ってコンポーザブルなコンポーネントを作成し、
00:15:04余分なJavaScriptを避ける方法がわかったので、
00:15:08最後の問題に進みましょう。
00:15:10もう一度これを閉じましょう。
00:15:13それは静的レンダリング戦略の欠如ですね?
00:15:18ビルド出力を見ると、実際にはすべてのページが動的ページになっています。
00:15:24つまり、ここで何かを読み込むたびに、これはすべてのユーザーに対して実行されることになります。
00:15:29すみません。
00:15:30これを開くすべてのユーザーが、この読み込み状態を受け取ることになります。
00:15:33サーバーコストを無駄にし、パフォーマンスを悪化させます。
00:15:37そして、
00:15:37それは私のページ内の何かが、
00:15:39すべてのページで動的レンダリングを引き起こしている、
00:15:43あるいは強制していることを意味します。
00:15:45実際には、ルートレイアウトの中にあります。
00:15:49これを経験したことがあるかわかりませんが。
00:15:51ここにあります。
00:15:53ヘッダーにこのユーザープロフィールがあります。
00:15:57そして、
00:15:57これはもちろんクッキーを使って現在のユーザーを取得しており、
00:16:00つまり他のすべてが動的にレンダリングされているということです。
00:16:02なぜなら、繰り返しになりますが、ページは動的か静的かのどちらかになり得ますよね?
00:16:06これはかなり一般的な問題で、以前のNextのバージョンでも解決されてきたことなので、どうすればよいか見てみましょう。
00:16:13一つの方法は、
00:16:14ルートグループを作成し、
00:16:16アプリを静的セクションと動的セクションに分割することです。そうすれば、
00:16:20Aboutページを抽出できます。
00:16:23これを静的にレンダリングできます。
00:16:25一部のアプリでは問題ありませんが、
00:16:27私の場合、
00:16:28重要なページは製品ページであり、
00:16:30これはまだ動的であるため、
00:16:32あまり役に立ちません。
00:16:33この戦略はどうでしょうか?
00:16:35ここでは、
00:16:35URLに特定の状態をエンコードするリクエストコンテキストパラメーターを作成し、
00:16:40`generate static params`を使ってページのすべての異なるバリアントを生成できます。
00:16:46それは実際、
00:16:47クライアントサイドでユーザーデータをフェッチすることと組み合わせることで、
00:16:51製品ページでこれをキャッシュできるようになります。
00:16:54確かに実行可能なパターンです。
00:16:55Vercel Flags SDKで推奨されている「プリコンピュートパターン」と呼ばれるものだと思います。
00:17:00しかし、これは非常に複雑で、データをフェッチする方法が複数あります。
00:17:04そして実際、アプリ全体をこれに書き換えたいわけではありません。
00:17:07では、これらの回避策を何もする必要がなかったらどうでしょうか?
00:17:10もっと簡単な方法があったら?
00:17:12ええ、あります。
00:17:14もう一度アプリケーションに戻りましょう。
00:17:17実際、`next.config`に移動して、`cache components`を有効にするだけです。
00:17:23おお、いいですね。
00:17:25さて、基調講演でご存知のように、これはすべての非同期呼び出しをリクエスト時、つまり動的にオプトインさせます。
00:17:34そして、
00:17:34中断されていない非同期呼び出しがあるたびにエラーを出し、
00:17:38ページ、
00:17:39関数、
00:17:40またはコンポーネントをきめ細かくキャッシュするために使用できる`use cache`ディレクティブを提供します。
00:17:48ええ、ではこれを利用しましょう。
00:17:51ここではホームページから始めましょう。
00:17:55見てみましょう。
00:17:56繰り返しになりますが、ここには静的コンテンツと動的コンテンツが混在しています。
00:17:59私のためのウェルカムバナーがあり、あなたのため、そして私のためにも何かがあります。
00:18:03このUIヘルパーを使って、もう一度見てみましょう。
00:18:06例えば、バナーはここで動的にレンダリングされます。
00:18:10一方、ヒーローは非同期でデータをフェッチしていて、かなり遅いため、これをハイブリッドレンダリングとしてマークしました。
00:18:18しかし、ユーザーデータや動的APIには一切依存していません。
00:18:21つまり、ここでハイブリッドレンダリングされているものはすべて、リクエスト間やユーザー間で再利用できるということです。
00:18:27そして、それに対して`use cache`ディレクティブを使用できます。
00:18:30では、ここに`use cache`ディレクティブを追加し、これをキャッシュ済みとしてマークしましょう。
00:18:35そうすれば、このページをリロードするたびに...保存していませんでした。
00:18:43ほらね。
00:18:44これはキャッシュされているので、この部分はリロードされません。
00:18:47今は静的ですよね?
00:18:49また、
00:18:50キャッシュタグのような関連APIもあり、
00:18:53これによりこれを型付けしたり、
00:18:55特定のキャッシュエントリをきめ細かく検証したり、
00:18:59再検証期間を定義したりできます。
00:19:01しかし、このデモでは、単純なディレクティブに焦点を当てましょう。
00:19:05この`use cache`ディレクティブがあるので、このヒーローの周りのSuspense境界を実際に削除できます。
00:19:10そして、
00:19:11それは...つまり、
00:19:12これにより部分的な事前レンダリングがこれを静的に事前レンダリングされたシェルに含めることができるため、
00:19:20このヒーローは、
00:19:21この場合、
00:19:21私のビルド出力の一部になります。
00:19:23このページで共有できる他のすべてについても同じことをしましょう。
00:19:28例えば、こちらにフィーチャーカテゴリがあります。
00:19:31では、そこでも同じことをしましょう。
00:19:33そして、`use cache`ディレクティブを追加し、これをキャッシュ済みとしてマークします。
00:19:37こんな風に。
00:19:39そして、Suspense境界を削除できます。
00:19:40これはもう必要ありません。
00:19:43フィーチャープロダクトも同様です。
00:19:44`use cache`を追加し、これをキャッシュ済みとしてマークしましょう。
00:19:48おっと。
00:19:50そして、Suspense境界を削除します。
00:19:52ここでどれだけの複雑さを取り除くことができたか、注目してください。
00:19:55以前やっていたスケルトンや累積レイアウトシフトについて心配する必要がなくなりました。
00:20:00そして、ページはもはや...というか、ページレベルでの静的と動的の制限がなくなりました。
00:20:07ですから、
00:20:08このページを読み込むと、
00:20:10真にユーザー固有のコンテンツを除いて、
00:20:12ここにあるすべてがキャッシュされているのがわかるでしょう。
00:20:16そうですね。
00:20:18それはかなりクールですね。
00:20:19「閲覧」ページに行って、そこでも同じことをしましょう。
00:20:24ええ。
00:20:25何が起こっているか簡単に理解できるように、すべての境界をすでにマークしました。
00:20:29そして、少なくともこれらのカテゴリをキャッシュしたいです。
00:20:33しかし、エラーが出ているようです。
00:20:37これに見覚えがあるかもしれません。
00:20:38つまり、ブロッキングルートがあるということです。
00:20:40そして、Suspense境界を使うべきなのに使っていません。
00:20:43これをリフレッシュすると、本当ですね?
00:20:46これは本当に遅いです。
00:20:47そして、パフォーマンスの問題と悪いUXを引き起こしています。
00:20:50これは素晴らしいです。
00:20:51`use cache`またはキャッシュコンポーネントが、ブロッキングルートを特定するのに役立っています。
00:20:55実際、その中で何が起こっているか見てみましょう。
00:20:57これが問題ですよね?
00:20:59これらのカテゴリをトップレベルでフェッチしており、その上にSuspense境界がありません。
00:21:03基本的に、選択をする必要があります。
00:21:05上にSuspense境界を追加するか、キャッシングにオプトインするかです。
00:21:09まず簡単なことをしましょう。ここに`loading.tsx`を追加するだけです。
00:21:12そして、ここに読み込みページ、素敵なスケルトンUIを追加しましょう。
00:21:21かなり良いですね。
00:21:21エラーは解決しましたが、待っている間、このページで何も役立つことは起こっていません。
00:21:25検索すらできません。
00:21:27ですから、キャッシュコンポーネントでは、動的は...あるいは静的対動的は、スケールのようなものです。
00:21:33そして、ページにどれだけの静的要素を持たせるかは、私たち次第です。
00:21:37では、このページをより静的にシフトし、この`loading.tsx`をもう一度削除しましょう。
00:21:43そして、以前学んだパターンを利用して、このデータフェッチをコンポーネントに押し込み、UIと共存させましょう。
00:21:50ですから、これをこちらのレスポンシブカテゴリフィルターに移動させます。
00:21:54レスポンシブデザインなので2つあります。
00:21:57実際、ここに追加するだけです。
00:22:01おっと。
00:22:03そしてこれをインポートします。
00:22:05このプロンプトはもう必要ありません。
00:22:06実際、私のコンポーネントはよりコンポーザブルになっています。
00:22:09そして、これを中断する代わりに、`use cache`ディレクティブを追加しましょう。
00:22:14それで十分なはずです。
00:22:16これによって、
00:22:17プロミスをどこで解決するかについてより深く考えることを強いられ、
00:22:20実際にコンポーネントアーキテクチャを改善していることに注目してください。
00:22:24これを中断する必要はありません。
00:22:25これは、ここの静的シェルに含まれるだけです。
00:22:28製品リストは、これを常に最新の状態に保ちましょう。
00:22:35ですから、毎回それをリロードできます。
00:22:37一方、下部のカテゴリもキャッシュしたいです。
00:22:41では、フッターに行きましょう。
00:22:44そして、
00:22:44ここでドーナツパターンを使っているので、
00:22:48これはインタラクティブなUIの一部の中にあったとしても、
00:22:52実際にキャッシュできます。
00:22:54ですから、これは全く問題ありません。
00:22:55そのパターンは、コンポジションだけでなく、キャッシングにも優れていました。
00:22:58そこにもう一つエラーがあると思います。
00:23:03それが何なのか見てみましょう。
00:23:04まだこのエラーがあります。
00:23:08これは実際、これらの検索フレームが原因です。
00:23:10検索フレームは、ご存知の通り動的APIです。
00:23:12これはキャッシュできません。
00:23:13しかし、UIのより多くの部分を静的にするために、より深く解決できます。
00:23:18では、これを下に移動させ、プロミスとして製品リストに渡しましょう。
00:23:24これをプロミスとして型付けします。こんな風に。
00:23:30製品リスト内で解決し、解決された検索パラメーターをこちらとこちらで使用しましょう。
00:23:36そして、これがここで中断されているので、エラーはなくなります。
00:23:40ですから、これをリロードすると、ここでリロードされるのは、私が特に動的に選択した部分だけです。
00:23:48他のすべてはキャッシュできます。
00:23:49そして、
00:23:49それはバナーとインタラクトしたり、
00:23:51検索したりできることを意味します。その部分はすでに事前レンダリングされているからです。
00:23:57さて、最後のページ、最も難しく最も重要な製品ページをやりましょう。
00:24:05今は本当にひどい状態です。
00:24:08これはEコマースプラットフォームにとって非常に重要です。
00:24:11よし、それも修正しましょう。
00:24:15ここに製品ページがあります。
00:24:18ここでは、再利用可能なコンテンツ、例えば製品自体だけをキャッシュし始めましょう。
00:24:23そして、ここに`use cache`を追加し、これをキャッシュ済みとしてマークします。
00:24:27それで問題ないはずです。
00:24:28つまり、ここのSuspense境界を削除できます。
00:24:33よし、そしてこれはもう、ここでのリクエストごとにリロードされることはありませんよね?
00:24:38製品詳細についても、同じことをしましょう。
00:24:40`use cache`を追加しましょう。
00:24:41キャッシュ済みとしてマークして、それも機能するか見てみましょう。
00:24:47できませんでした。
00:24:48実際、これは別のエラーです。
00:24:50このキャッシュされたセグメント内で動的APIを使用しようとしていると告げています。
00:24:54そして、それは本当です。
00:24:54「製品を保存」ボタンを使っていますよね?
00:24:56それで、クリックして保存状態を切り替えることができました。
00:25:00では、これで何ができると思いますか?
00:25:03またドーナツパターンを使うことができます。
00:25:06実際、動的セグメントをキャッシュセグメントにスロットインすることもできます。
00:25:10ですから、以前と同じように、キャッシュを使ってそれらをインターリーブしています。
00:25:12これはかなりクールですね。
00:25:14では、ここに`children`をこのように追加しましょう。
00:25:19そして、これでエラーがなくなります。
00:25:21そして、
00:25:21このページのこの1つの動的セグメントの周りにこれをラップし、
00:25:26Suspense境界を削除して、
00:25:28そのページの1つの動的ピースのために非常に小さなブックマークUIを追加するだけです。
00:25:34では、それが今どう見えるか見てみましょう。
00:25:40ほとんどすべてのUIが利用可能ですが、この小さな動的な部分が1つだけあることに注目してください。それは問題ありません。
00:25:47他のすべてはまだそこにあります。
00:25:48そして、レビューは動的にしておきましょう。それらを常に最新の状態に保つことができるからです。
00:25:53まだもう一つエラーがあります。
00:25:54それを素早く解決しましょう。
00:25:56繰り返しになりますが、これは`params`です。
00:25:58読み込みフォールバックを追加するか、これをキャッシュするかの選択をする必要があるという助けを得ています。
00:26:04この場合は`generate static params`を使いましょう。
00:26:07ユースケースとデータセットによりますが。
00:26:10しかし、
00:26:10このケースでは、
00:26:11事前にレンダリングされた定義済みのページをいくつか追加し、
00:26:14残りはユーザーによって生成されるときにキャッシュするだけです。
00:26:17そして、これでここでのエラーがなくなります。
00:26:20これで、リファクタリングは実際に完了したと思います。
00:26:22では、デプロイされたバージョンを見て、それがどうなっているか確認しましょう。
00:26:26Vercelにデプロイしました。
00:26:27そして、覚えておいてください。ここでは多くのデータフェッチを意図的に遅くしました。
00:26:35それでも、このページを最初に読み込むと、すべてがすでに利用可能です。
00:26:40ここにあるのは、割引や「あなたへのおすすめ」のような、ごく一部の動的セグメントだけです。
00:26:46「すべて閲覧」も同様です。
00:26:47すべてのUIはすでに利用可能です。
00:26:50そして、製品自体については、瞬時に感じられます。
00:26:54そして、
00:26:55繰り返しになりますが、
00:26:56これらのキャッシュされたセグメントはすべて、
00:26:58部分的な事前レンダリングによって静的シェルに含まれます。
00:27:00そして、新しいNext 16クライアントルーターで改善されたプリフェッチを使って、事前にフェッチできます。
00:27:05つまり、すべてのナビゲーションが...とても速く感じられますよね?
00:27:09さて、まとめると、キャッシュコンポーネントを使えば、静的対動的という区別はもうありません。
00:27:17そして、動的APIを避けたり、動的コンテンツを妥協したりする必要もありません。
00:27:28そして、
00:27:28私が示したように、
00:27:30このキャッシュヒットのためだけに複数のデータフェッチ戦略を使うような複雑なハックや回避策をスキップできます。
00:27:37ですから、最新のNext.jsでは、動的対静的はスケールのようなものです。
00:27:40そして、アプリにどれだけの静的要素を持たせるかは、私たち次第です。
00:27:43そして、
00:27:43特定のパターンに従う限り、
00:27:45パフォーマンスが高く、
00:27:46コンポーザブルで、
00:27:47デフォルトでスケーラブルな一つのメンタルモデルを持つことができます。
00:27:50では、スライドに戻りましょう。
00:27:53もし、そのスピードにまだ感銘を受けていないなら、これがLighthouseスコアです。
00:27:56Vercel Speed Insightsでフィールドデータを収集しました。
00:28:00ですから、
00:28:00最も重要なページであるホームページ、
00:28:02製品ページ、
00:28:03製品リストのすべてで100点を獲得しました。これらは非常に動的であるにもかかわらずです。
00:28:08では最後に、
00:28:09Next.jsアプリでスケーラビリティとパフォーマンスを確保し、
00:28:13最新のイノベーションを活用してこのようなスコアを得るためのパターンをまとめましょう。
00:28:18まず、
00:28:19コンポーネントツリーの深いところでプロミスを解決し、
00:28:21React Cacheを使ってコンポーネント内でローカルにデータをフェッチすることで、
00:28:26重複作業を減らし、
00:28:27アーキテクチャを洗練できます。
00:28:28Context ProviderとReact Useを組み合わせることで、
00:28:31クライアントコンポーネントへの過剰なプロップ渡しを避けることができます。
00:28:35第二に、
00:28:35ドーナツパターンを使ってサーバーコンポーネントとクライアントコンポーネントを構成することで、
00:28:39クライアントサイドJavaScriptを削減し、
00:28:41関心の明確な分離を保ち、
00:28:42コンポーネントの再利用を可能にします。
00:28:43そして、このパターンは、後で構成されたサーバーコンポーネントをキャッシュすることをさらに可能にします。
00:28:50そして最後に、
00:28:50`use cache`を使ってページ、
00:28:52コンポーネント、
00:28:52または関数のいずれかをキャッシュおよび事前レンダリングすることで、
00:28:55冗長な処理を排除し、
00:28:56パフォーマンスとSEOを向上させ、
00:28:57部分的な事前レンダリングによってアプリのこれらのセグメントを静的にレンダリングさせることができます。
00:29:01そして、コンテンツが真に動的である場合は、適切な読み込みフォールバックで中断させることができます。
00:29:07そして、これらすべてが繋がっていることを覚えておいてください。
00:29:09ですから、
00:29:09アーキテクチャが優れていればいるほど、
00:29:11構成が容易になり、
00:29:12最良の結果でキャッシュおよび事前レンダリングが容易になります。
00:29:15例えば、ツリーの深いところで動的APIを解決することで、より大きな部分的にレンダリングされた静的シェルを作成できます。
00:29:22ということで、これが完成版アプリケーションのリポジトリです。
00:29:25そこにはお見せできなかったたくさんのものがあるので、ぜひチェックしてみてください。
00:29:29写真を撮って自分で入力したくない場合は、
00:29:31QRコードをスキャンして、
00:29:33リポジトリと一緒に私のソーシャルメディアを見つけることができます。
00:29:36ええ、私からは以上です。
00:29:37Next.js Conf、ここにお招きいただきありがとうございます。
00:29:39[音楽再生中]

Key Takeaway

Next.js 16の`use cache`ディレクティブと、データフェッチの最適化、ドーナツパターンなどのアーキテクチャパターンを組み合わせることで、静的と動的の境界をなくし、パフォーマンスとスケーラビリティを最大化したモダンなNext.jsアプリケーションを構築できます。

Highlights

Next.js 16の`use cache`ディレクティブにより、ページが静的と動的の両方になり、きめ細かなキャッシングが可能になった。

プロップドリリングを解消するため、React CacheとContext Providerを組み合わせ、データフェッチをコンポーネントツリーの深い位置に移動させる。

ドーナツパターンを用いて、サーバーコンポーネントとクライアントコンポーネントを構成し、クライアントサイドJavaScriptを削減し、関心の分離を保つ。

`use cache`を適用することで、冗長な処理を排除し、パフォーマンスとSEOを向上させ、部分的な事前レンダリングを活用する。

動的APIに依存する部分を適切に分離し、静的コンテンツと動的コンテンツを効率的に混在させることで、高速でスケーラブルなアプリケーションを構築できる。

Timeline

導入と静的・動的レンダリングの基礎

発表者は自己紹介後、Next.jsのApp Routerにおけるコンポジション、キャッシング、アーキテクチャのパターンについて説明すると述べます。まず、ウェブサイトのパフォーマンスとスケーラビリティを確保するための基本概念として、静的レンダリングと動的レンダリングの違いを解説します。静的レンダリングは事前レンダリングにより高速アクセス、サーバー負荷軽減、SEO向上に寄与し(例:Next.js Confサイト)、動的レンダリングはリアルタイムデータやパーソナライズされたコンテンツ(例:Vercelダッシュボード)を可能にします。`params`、`search params`、`cookies`、`headers`、`fetch`と`no-store`などのAPIが動的レンダリングを引き起こす具体的な例として挙げられます。

従来のNext.jsの課題と`use cache`の登場

以前のNext.jsでは、ページ全体が静的か動的かのどちらかに強制され、一つの動的APIがページ全体を動的にしてしまう問題がありました。これにより、ほとんど静的なコンテンツに対してもサーバーで冗長な処理が発生し、パフォーマンスが低下していました。ReactサーバーコンポーネントとSuspenseは部分的な解決策でしたが、動的コンテンツのストリーミングには追加のスケルトンUI作成などが必要でした。この問題を解決するため、Next.js 16で`use cache`ディレクティブが導入され、ページが静的と動的の両方になり得ることが発表されました。`use cache`はコンポーザブルなキャッシングを可能にし、ページ、Reactコンポーネント、または関数をキャッシュ可能としてマークできます。

デモアプリケーションの紹介と一般的な問題

発表者は、eコマースプラットフォームを模したNext.jsアプリケーションのデモを開始します。このアプリには、ホームページ、製品閲覧ページ、製品詳細ページ、Aboutページ、ダッシュボードなどがあり、静的コンテンツとユーザーに依存する動的コンテンツが混在しています。現在のコードベースが抱える一般的な問題として、深いプロップドリリングによる保守・リファクタリングの困難さ、冗長なクライアントサイドJavaScriptと複数の責任を持つ大規模コンポーネント、そして静的レンダリングの欠如による追加のサーバーコストとパフォーマンス低下が挙げられます。デモの目標は、これらの問題を解決し、アプリをより高速でスケーラブル、かつ保守しやすいものにすることです。

プロップドリリングの解決とコンポーネントアーキテクチャの改善

最初の問題であるプロップドリリングに対処するため、発表者は`loggedIn`変数の渡し方を改善します。データフェッチをコンポーネントツリーのより深い位置に押し下げ、React CacheとContext Provider(`AuthProvider`と`useAuth`フック)を組み合わせることで、プロップドリリングを回避し、コンポーネントの再利用性を高めます。これにより、ウェルカムバナーのようなコンポーネントがホームページの依存関係から解放され、ブラウザページなど他の場所でも簡単に再利用できるようになります。このパターンは、優れたコンポーネントアーキテクチャを維持し、コンポーネントをより使いやすく、コンポーザブルにするための鍵となります。

ドーナツパターンによるクライアントサイドJavaScriptの削減

次に、過剰なクライアントサイドJavaScriptと大規模コンポーネントの問題に取り組みます。発表者は「ドーナツパターン」を紹介し、インタラクティブなUIロジックをクライアントサイドのラッパーコンポーネント(例:`BannerContainer`)に抽出し、その中にサーバーレンダリングされたコンテンツをスロットインする方法を示します。これにより、ウェルカムバナーのようなコンポーネントをサーバーコンポーネントに変換し、データフェッチをサーバーサイドで行うことで、クライアントサイドJSを大幅に削減します。また、アニメーションライブラリ(Motion)の使用で発生する`use client`の強制も、同様のドーナツパターンで解決し、フッターの`ShowMore`機能やモーダルにもこのパターンを適用できることを示します。

静的レンダリング戦略の欠如と`use cache`による解決(ホームページ)

アプリケーションのすべてのページが動的にレンダリングされている問題に対処します。これは、ルートレイアウト内のユーザープロフィールがクッキーを使用しているため、ページ全体が動的に強制されていることが原因です。従来の回避策(ルートグループ分割やプリコンピュートパターン)の複雑さを指摘し、より簡単な解決策として`next.config`で`cache components`を有効にすることを提案します。これにより、すべての非同期呼び出しがデフォルトで動的にオプトインされ、`use cache`ディレクティブを使ってページ、関数、コンポーネントをきめ細かくキャッシュできるようになります。ホームページのヒーロー、フィーチャーカテゴリ、フィーチャープロダクトに`use cache`を適用し、Suspense境界を削除することで、これらのセグメントが静的に事前レンダリングされたシェルに含まれることを実演します。

`use cache`による解決(製品閲覧ページと製品詳細ページ)

製品閲覧ページで、トップレベルのカテゴリフェッチがブロッキングルートとなりエラーが発生する問題に直面します。`use cache`がブロッキングルートの特定に役立つことを示し、データフェッチをコンポーネントに押し込み、`use cache`を適用することで解決します。これにより、製品リストの検索パラメーターも深く解決できるようになり、ページ全体の静的要素が増加します。最も重要な製品詳細ページでは、製品自体と製品詳細に`use cache`を適用しますが、動的API(「製品を保存」ボタン)の使用によりエラーが発生します。ここでもドーナツパターンを再利用し、動的セグメントをキャッシュセグメント内にスロットインすることで、問題を解決します。最後に、`params`による動的レンダリングに対して`generate static params`を使用し、一部のページを事前レンダリングし、残りをユーザー生成時にキャッシュすることで、すべてのエラーを解消します。

デプロイ結果とモダンNext.jsのパターンまとめ

リファクタリング後のアプリケーションをVercelにデプロイし、意図的に遅延させたデータフェッチにもかかわらず、ホームページ、製品閲覧ページ、製品詳細ページが非常に高速にロードされることを実演します。`use cache`によってキャッシュされたセグメントが部分的な事前レンダリングで静的シェルに含まれ、Next.js 16のクライアントルーターのプリフェッチ機能と相まって、ナビゲーションが瞬時に感じられることを強調します。結論として、`use cache`により静的と動的の区別がなくなり、複雑な回避策が不要になることを述べます。最後に、データフェッチの最適化、ドーナツパターン、`use cache`の3つの主要なパターンをまとめ、これらが優れたアーキテクチャ、容易なコンポジション、最適なキャッシングに繋がり、高いLighthouseスコア(全ページ100点)を達成できることを示し、リポジトリへのリンクを提供して締めくくります。

Community Posts

View all posts