🖊️

Next.jsにおけるAPIリクエスト処理の設計と状態管理の選択肢

2025/03/13に公開

記事の対象者

Next.jsアプリの初学者の向けになっている(僕自身がNext.js初学者)
Rails+Next.jsという構成のアプリにおいて、Next.js→Railsにリクエストを送る際、どのような設計がいいのか疑問に思ったので調べた結果をまとめた

APIリクエストの設計パターン

パターンとしては、以下の2つがある

  • Reactコンポーネントからリクエストする
  • API Routesを経由してリクエストする

それぞれのメリット・デメリットは以下の通りになりそうである

コンポーネントからRails APIへ直接リクエストする場合

Next.jsのフロントエンド(Reactコンポーネント)から、RailsのAPIエンドポイントへ直接HTTPリクエストを送る方法
例えばfetchやaxiosを用いてブラウザからRailsのJSON APIにアクセスし、取得したデータをコンポーネントのステートに保存する

メリット

中間サーバーを挟まないため構成がシンプルで、余計なオーバーヘッドがない
フロントエンドから直接通信するだけなので実装が容易であり、レスポンスの遅延も最小限(ネットワーク往復は1回だけ)となる
Rails側のAPIさえ用意すればすぐに通信でき、Next.js側で特別な設定は不要で手軽である

デメリット

フロントとバックエンドが別オリジンの場合はCORSの許可設定が必要になるなど、クロスオリジン通信の対応が発生する
また、ブラウザから直接リクエストを送るため、APIエンドポイントやトークン等がエンドユーザに見えてしまう点に注意が必要である
Railsのエンドポイントを隠蔽できないので、必要に応じてAPIキーの管理や通信内容の暗号化を検討する必要が出てくる
さらに、複数のフロントエンドから同じAPIを呼ぶ場合にログが分散しがちで、リクエストの集中管理や分析がしにくい側面も出てくる

Next.jsのAPI Routesを経由してリクエストする場合

Next.jsのAPI Routes機能(/pages/api/*)をバックエンドとの中間層(いわゆるBFF: Backend for Frontend)として利用するパターン
フロントのコンポーネントは一旦Next.js内のAPIルートにリクエストを送り、そこでRailsのAPIに対する通信を行って、結果をフロントに返す(Next.jsサーバーがプロキシのように振る舞う)

メリット

フロントからRails本体への直接アクセスを避けられるため、セキュリティ向上する
外部からRailsのエンドポイントを直接叩けないようにでき、リクエストヘッダに含まれるトークン等の機密情報を隠蔽できる
また、すべてのAPI呼び出しがNext.js側に集約されるためリクエストログを一元管理でき、モニタリングやデバッグがしやすくなる
さらに、フロントとバックエンドの間のインタフェースを抽象化できるため、将来バックエンドのエンドポイント構成が変わっても、Next.js側のAPIルートを調整するだけでフロントの実装変更を最小限に抑えられる
場合によってはNext.js側で複数のRails API呼び出し結果をまとめてから返すなど、BFF的な拡張も可能となる

デメリット

中間APIの実装・管理コストが増加する
フロント・バックエンドに加えてAPIルートのコードを書いて維持する手間が発生し、エンドポイントの数も増えるため全体の構成が複雑になる
また、フロント→Next API→Railsという余分なネットワーク経路が増えるぶん遅延が大きくなり、サーバーレス環境では呼び出しごとのコストも増える
(※Vercel等ではAPI Routesはサーバーレス関数として実行されるため、その分の実行時間課金が発生し得える)
コードが分散することでデバッグや保守も難しくなる可能性があり 、Rails APIを直接呼ぶ場合と比べて開発工数が増大する

補足

Next.jsのAPI RoutesはNode.js環境で動作するため、Railsとの通信にサーバー間通信の利点(高速な内部ネットワークや認証情報の安全な保持)を活かせるが、同時にRails側でCORSを厳しく制限してしまうとNextサーバーからのアクセスもブロックされる点に留意が必要となる

各方式の比較と選択指針

「直接リクエスト」と「API Routes経由」のいずれにも長所短所がある
特別なセキュリティ要件や複雑な連携処理が不要な場合、基本的にはフロントから直接RailsのAPIを呼ぶ方がシンプルで十分と言える
一方で、認証情報を隠したい場合やバックエンドAPIを集中的に管理する必要がある場合には、Next.jsのAPI Routesを挟むメリットが大きくなる
プロジェクトの規模や要求に応じて、まずはシンプルな直接通信で実装・検証し、必要に応じて中間API方式に移行するのがおすすめとなる

状態管理の選択肢

フロントエンド側で取得したデータをどのように管理し、コンポーネント間で共有するかも重要となる
Next.js(React)では以下のような状態管理手法・ライブラリが代表的である。それぞれ特性が異なるため、用途に応じた選択が求められる

React Query(TanStack Query)

非同期のサーバー状態(リモートデータ)管理に特化したライブラリ
データのフェッチ処理を宣言的に扱え、読み込み中・エラー状態の管理やキャッシュ、自動リフレッシュ等を包括的に解決する
例えばRails APIからのデータ取得をuseQueryフックで呼び出すと、キャッシュのおかげで同じクエリキーのデータは複数コンポーネントで一度のフェッチで共有され、更新時もバックグラウンドで効率よく再取得される
そのため頻繁にサーバーデータを読み書きする場合や、データ取得のためにReduxを使っていたケースの代替として非常に有効らしい
React Queryはあくまでサーバーから取得するデータの同期・キャッシュに強みを持つため、ユーザ入力フォームの内容や一時的なUI状態の管理には別手段と組み合わせることになる

Redux

グローバルなアプリケーション状態を一元管理するための古典的なライブラリ
複数のコンポーネントにまたがる複雑なUI状態や、ユーザー操作に応じた一連の状態遷移(例: ウィザード形式のフォーム、モーダルの開閉、通知の管理など)を扱うのに適している
Redux自体は同期的な状態管理が主目的だが、ミドルウェア(Redux ThunkやSaga等)を追加することでAPIコールなど非同期処理を組み込んだ高度なロジックも実現できる
大規模なアプリや状態遷移が複雑なケースではReduxによる厳格な管理が有効で、再レンダリングの制御も最適化しやすい利点がある
ただし、小規模な用途や単純なデータ取得のためだけにReduxを導入すると、ボイラープレート(アクションやリデューサーの記述)が増えて開発コストが高くなる傾向がある
近年はRedux ToolkitやRTK Queryの登場でかなり簡潔に書けるようになったが、「サーバーから取得したデータの保持」が主目的であればReact Queryのような専用ライブラリの方が手軽な場合も多い

Context API

Reactにビルトインのグローバル状態共有機構
コンテキストを使うと、ツリー上の深い子コンポーネントに対してプロパティで渡すことなく状態や値を提供できる
アプリの規模がそれほど大きくなく、グローバルに共有する状態もせいぜいユーザー情報やテーマ設定程度であれば、追加ライブラリ不要のContext APIで十分対応可能
Context自体はシンプルな仕組みですが、状態を保持・更新するには内部でuseStateやuseReducerを用いる必要があり、更新頻度の高い状態をContextで管理すると全ての消費コンポーネントが再レンダリングされるためパフォーマンス上のボトルネックになることがあ
またReduxのようなミドルウェア機能は無いため、非同期処理やロジックは各コンテキストプロバイダ内で独自実装しなければならない
グローバル状態の管理機能だけで足りる小規模アプリには適しますが、状態種類が増えてコンテキストが乱立したり複雑なロジックが必要になった場合は、他のライブラリへの移行を検討する必要が出てくる

どの状態管理を使うべきかはケースバイケースだが、一般的に「サーバーから取得するデータ」はReact Queryなどで管理し、「UIの操作に伴うクライアント状態」はReduxやContextで管理するという棲み分けがされている
例えばRails APIからのデータ取得・キャッシュにはReact Queryを用い、ユーザーのログイン状態やUIのフィルタ条件などは必要に応じてContextやReduxで保持する、といった構成となる
アプリの複雑さが増した場合にはRedux等の採用も検討しつつ、まずはReact Query + 最小限のContextで実装し、不足を感じたら拡張していくのが良さそう

参照元

https://zenn.dev/sutamac/articles/624aba9a62c52a
https://www.reddit.com/r/nextjs/comments/1bq9ygr/direct_backend_api_calls_vs_nextjs_api_routes/#:~:text=•
https://qiita.com/suin/items/e2df562b0c2be7e2a123#:~:text=TanStack Query
https://zenn.dev/luvmini511/articles/61e8e54853bc13
https://stackoverflow.com/questions/68525459/what-is-the-main-difference-between-react-query-and-redux#:~:text=That is where react
https://forum.freecodecamp.org/t/redux-or-react-query-or-context-api/487388

Discussion