Closed4

React, Next.jsについて理解を深める

mrmsmrms

React, Next.jsについておさらい

React, Next.jsについて、業務で必要になったためキャッチアップを行ったが、なぜ必要でどういう役割があるかなどが整理できていない(忘れてきた)ため、AIに質問したり、個別の記事などからおさらいする。

mrmsmrms

Reactについて

そもそもなぜ必要なのか?

理由1. コンポーネントベースで開発できるため、再利用しやすくコードの保守性が高まるから。

使用しないと、現代のWebアプリを作ろうとした時に複雑になってしまう。

理由2. コンポーネントの再利用ができるため、UIに一貫性を持たせやすくなる

これは使っていて実感できる。

理由3. 大規模アプリケーションではコードが理解しやすくなり、DOM操作もReact側がやってくれる

仮想DOMのため、コードで直性DOM操作をしなくて良いからコードは簡潔になる。
DOM操作をReactに任せることでバグも発生しずらい。
逆に直接DOM操作を行うようなコードは書いてはいけない。
React側が仮想DOMを管理し、変更された部分のみ更新するなどパフォーマンスの向上につながる。

Reactのみで実装した場合のレンダリング方法は?

Reactはクライアントサイドのライブラリのため、デフォルトでCSRのみサポートしている。
CSRは、DOMの構築もクライアントサイドで行うため、初期ロードに時間がかかる。

CSRの向き不向き

CSRが向いているシーン

  • リアルタイム性が要求される場合
    ページ更新時にページ全体ではなく特定のDOMのみを更新できるため、変更内容が即座に反映できる。
    処理がほどんどクライアントサイドで行われるため、サーバーとの通信を最小にできる。
    上記の理由により、サーバー側の負荷も抑えられる。

CSRが不向きなシーン

  • SEO重視のページ
    初期HTMLにほとんど内容がなく、JSによってコンテンツが生成されるため、クローラーがJSを実行しない場合にサイトに関する情報がほとんど取得できない。

  • 小規模な静的サイト
    静的なサイトではReactによる恩恵が受けれず、過剰な実装となってしまう。
    小規模なサイトにも関わらず、コンテンツのダウンロードサイズが大きくなってしまいページの表示に時間がかかってしまう。

  • パフォーマンスに制約がある場合
    クライアントサイドで処理を行うため、利用する端末の性能が低い場合は向いていない。
    また、同様の理由で初期表示速度が要求される場合も向いていない。
    回線的に大量のJSコードがダウンロードできない場合も向いていない。

mrmsmrms

Next.jsについて

そもそもなぜ必要なのか?

Reactのみでは実現が難しい以下の機能を提供するため。

SSR

Reactのみではクライアント側でレンダリングを行っていたが、SSRによりリクエスト時にサーバー側でHTMLを生成することで初期表示を高速にできる。

SSG

ビルド時にHTMLを生成することで、CSRよりもページ表示が高速になる。

ファイルベースルーティング

Reactのみでは別途ライブラリを使用してルーティングを行う必要があるが、Next.jsによって自動でルーティングできる。
開発時にはファイルのディレクトリ構造がそのままパスになるため、ルーティングの設定を気にしなくて良い。

自動パフォーマンス最適化

next/imageコンポーネント

  • WebPやAVIFなどの最新フォーマットを自動的に使用
  • デバイスに応じた適切なサイズの画像を提供
  • 画像の遅延読み込みを自動的に実行
  • レイアウトシフトの防止

next/scriptコンポーネント

  • 外部スクリプトの実行順序を制御
  • strategyプロパティによる優先順位の自動設定
  • パフォーマンスへの影響を最小限に抑制

Next.jsの使い方に沿っていないとこの恩恵を受けれない点で注意が必要かと。

SEO対策

SSR、SSGによってJSを実行しないクローラーに対応できる。

Next.jsについて勘違いしていたこと

Next.jsは、フロントエンドに特化したReactベースのフレームワークであり、APIサーバーではない
サーバー機能を内包しているため勘違いしていた。
バックエンド機能を実装することも可能だが、推奨されていない

このあたりについて、AIによる情報は以下のとおり

Next.jsサーバーの役割

主な機能

  • Webブラウザへのコンテンツ配信を行います
  • サーバーサイドレンダリング(SSR)の実行
  • 静的サイト生成(SSG)の提供
  • APIルートを通じた簡易的なAPI機能の提供

バックエンドとの関係

アーキテクチャ上の位置づけ

  • バックエンドとは別の環境として動作します
  • 既存のバックエンドと併用することが可能です
  • 完全なバックエンドフレームワークではありません

推奨される使用方法

設計上の注意点

  • 小規模なプロジェクトではNext.jsのみでバックエンド機能を実装することも可能です
  • 重たい処理や精度が必要な処理は、専用のバックエンドサーバーに任せるべきです
  • APIサーバーとしての利用は推奨されていません

この内容が頭に入っているだけでもだいぶNext.jsに対する理解は深まるはず。

mrmsmrms

Next.js(App Router)を使う上で知っておきたいこと

AIに聞いた内容だけでは、実装時にどうすべきか迷ってしまうため、ある程度の指針が自分の中で確立されるようにさらに調査した。

参考にしたもの

以下の動画でNext.jsのベストプラクティスについてキャッチアップした。
自分は動画の方が理解しやすいと改めて実感。

動画の元となっている記事は以下。
https://zenn.dev/akfm/books/nextjs-basic-principle

以降の項目はキャッチアップした内容のまとめ。

コンポーネントについて

デフォルトはServer Component

Next.jsでは、何も考えずにコンポーネントを作成した場合、Server Componentになる。
よって、Client Componentで動作するライブラリやhooksが使えないので注意が必要。

どうしてもClient Componentにしたい

ファイルの先頭にuse clientをつける。
ただ、この方法ではこのコンポーネント配下のコンポーネントもすべてClient Componentになってしまうため、推奨されない。
使うとしてもコンポーネントツリーの末端で使用する。

ではどうするのが良いかというと、
ファイル全体をClient Componentにするのではなく、Client ComponentのchildrenとしてServer Componentを渡して使用する(Compositionパターン)。

Client Componentを使用する場面は?

  1. イベントハンドラ(onClick(),onChange())、hooksやブラウザAPIを使用する場合
  2. React Server Componentsに未対応な(Client Componentでしか使用できない)サードパーティライブラリを使用する場合
  3. RSC Payload転送量を削減したい場合
    Server Componentの場合、レスポンスとしてRSC Payloadが毎回転送されてくるため、コンポーネントが大きいとその分転送量も増える。
    これを解決するために、Server Componentではデータフェッチのみ、Client ComponentではReactElement部分のみとすることでRSC Payloadの転送量を削減するために使用する(Container/Presentationalパターン)。

データフェッチについて

データフェッチはServer Componentで行う

Reactのみの場合、サードパーティライブラリを使用したClient Componentでデータフェッチを行うことが推奨されているが、Next.jsを使用する場合はServer Componentで行う。
しかし、ユーザーの操作に伴うフェッチはServer Componentに向いていない。
なぜならServer ComponentはSSRであり、UXを損ねるためである。

ユーザーの操作に伴うフェッチはServer Actions + useActionState()

Client Component側でuseActionState()を使用し、Server Actionsを呼び出す方法である。
この方法はデータ操作を伴うフェッチには対応していない。

データ操作を伴うデータフェッチ

ユーザーの操作に伴うフェッチと同様、データフェッチ自体はServer Actionsで行い、Client Componentでそれを使用する。
更新内容を反映するため、Server Actions内でrevalidatePath()revalidateTag()を使用しキャッシュを再検証する必要がある。
Server Actionsに対応しているフォームライブラリとしてconformがある。

ただ、上記の方法ではリアルタイム性に弱いため、リアルタイム性が要求されるような場合はサードパーティライブラリを使用した実装を検討する。

キャッシュについて

Next.js App Routerではfetchが拡張されており、それを使用した場合、v14ではデフォルトでキャッシュされるようになっていたが、v15以降ではデフォルトでキャッシュされなくなっている。

Next.jsには4つのキャッシュが存在する。

Request Memoization

同一のURL・同一のオプションリクエストは、1つのリクエストにまとめられる。
これにより同じデータフェッチを各コンポーネントで行なっていても重複したリクエストにはならない。
つまり、各コンポーネントでデータフェッチの記述ができるため、親コンポーネントからバケツリレーによるデータの受け渡しを行う必要がなくなる。
データフェッチ部分のみを関数化しておくと良い。
キャッシュ期間はリクエスト毎。

Data Cache

データフェッチの結果(JSON)をサーバー側で永続的にキャッシュする。
このキャッシュは、リクエストやユーザーを超えて共有されるため注意。
ユーザー固有の情報ではなく共通の項目に対するデータフェッチ時に使用する。
fetchのcacheオプションをforce-cacheとすることでData Cacheを有効にできる。
revalidateにより再検証の期間を指定することも可能。
再検証の期間を指定しても期間後の初回リクエストはキャッシュされたものになり、裏でキャッシュが更新される。

Full Route Cache

Static Rendering(SSG・IRSに相当)の場合、HTMLやRSC Payloadをキャッシュする。
ビルド時と再検証時にキャッシュされる。
頻繁にデータが更新されるようなユーザー固有の情報を扱うページでなければ、こちらを利用することが推奨される。
デフォルトはStatic Renderingだが、Dynamic Rendering(SSRに相当)の場合はキャッシュされない。
cookies()/headers()といったDynamic Functionsを使用すると、Dynamic Renderingになるので注意。

Router Cache

これのみクライアント側のキャッシュ。
セッション中のキャッシュのため、ブラウザを閉じると破棄される。
ユーザーが訪問したページのRSC Payload(Server Componentsのレンダリング結果)をキャッシュしている。ページ更新するとクリアされる。
<Link>コンポーネントを用いたページ遷移では、静的ページの場合はデフォルトでリンクが見えた時点でprefetch(事前にロード)される。
prefetch属性をfalseにすることで無効にできる。
動的ページでは、動的な部分以外がprefetchされる。
キャッシュの期間はnextConfigのstaleTimesで制御できる。

キャッシュの再検証は以下の方法で可能。

  • Client Componentrouter.refresh()を呼び出す
  • Server ActionsでrevalidatePath()/revalidateTag()を呼び出す(データ操作を伴うデータフェッチでも言及)
  • Server Actionsでcookies.set()/cookies.delete()を呼び出す

Router Cacheの挙動は、ドキュメントにはないものもあるようなので目を通しておくのが良いかも。

このスクラップは6ヶ月前にクローズされました