🦁

App Router で1年間開発した知見と後悔

2024/08/05に公開

Next.js の App Router を 1 年間使用した経験をもとに僕なりの知見と後悔したことを共有しようと思います。

データフェッチング戦略(知見)

App Router では RSC 内でのフェッチのため分離しづらいです。なおかつ、以前より柔軟にサーバー側の処理を書けるため、より多くのコードが集まり肥大化しやすいです。

したがって Pages Router と比較して以下のような違いがあります。

Pages Router(SSR) App Router(RSC によるフェッチ)
getServerSideProps によりデータフェッチがきれいに分離される RSC 内でのフェッチのため分離しづらい

この違いにより、App Router ではデータフェッチのロジックを自身で抽象化する必要性が高まっています。

ベンチャーでは Next.js にすべてのロジックをもたせて、スピード感のある開発をしたい場合があります。それがメインのプロダクトの場合、将来的には肥大化を解消するために API サーバーを実装することも十分考えられます。そういった場合を想定しているなら抽象化しておいたほうが移行コストが下がっていいでしょう。

バックエンドのベストプラクティスの導入(知見)

Next.js の特徴として、従来のフロントエンド開発と異なり、バックエンド側のベストプラクティスを導入することが推奨されます。特にサーバーアクションは、公開されているエンドポイントとして機能するため、通常の API エンドポイントと同様にバリデーションを行うことが重要です。

具体的には、以下のようなバックエンドのベストプラクティスを導入しました:

  • 入力バリデーション: サーバーアクションに送信されるデータは、必ずバリデーションを行い、不正なデータが処理されないようにしました。
  • 認証と認可: 各サーバーアクションに対して、ユーザーの認証と認可を適切に行い、アクセス制御を強化しました。

コロケーションの導入(後悔)

初期には、ルーティングとコンポーネントを同じディレクトリに配置するコロケーションを導入しました。しかし、我々の場合、Pages Router の頃から整理されたコードベースが存在しました。その分離をやめてコロケーションにしたのですが、URL変更時の差分が増大するという問題が発生しました。

例えば以下のようなuserついてのディレクトリ構成があったとすると、userからusersに変えるだけで合計5ファイルが変更に含まれます。ここにコロケーションもしているとすると簡単に20ファイルくらいは超えるでしょう。もっと上の階層のurl構造を変更した場合は200,300くらいの変更も簡単に起こるでしょう。

├── user
│  ├── @delete
│  │  ├── default.ts
│  │  └── page.tsx
│  ├── @edit
│  │  ├── default.ts
│  │  └── page.tsx
│  └── layout.tsx

したがって現在は app ディレクトリ配下には Next.js によりさだめられたファイルのみを配置し、具体的な実装は独自に改良した package by feature のディレクトリ構成を採用しています。

これに関してはもともとコードベースが整理されていたから後悔したことも大きいと思います。むしろ整理されていない状態ならコロケーションにすることでより整理されたコードベースになることもあるので、自身のプロジェクトの状況に合わせて判断することが重要です。

Intercepting Routes の使用(後悔)

Next.js の新機能である Intercepting Routes はまだ未解決のバグが多く、不要な混乱をもたらす可能性があるため、使用を控えるべきです。ドロワーやモーダルは従来の方法で実装するほうが無難です。
我々も細かい部分でバグがあり、後悔していることのほうが多いです。

GraphQL クライアントについて(知見)

現在は Apollo Client を使用しています。
我々はキャッシュ戦略として Next.js の Router Cache を使用しているため、Apollo Client のキャッシュ機能は不要です。Apollo Client の目玉といえばきめ細かいキャッシュ制御だと思うので、それを使わないなら too much な技術と言えるでしょう。

移行先としては urql を考えています。dedup もあり、apollo client よりも軽量であるためです。

プロジェクト次第では graphql-request も視野に入れる価値があります。Next.js での fetch が高機能化しているため、graphql-request を使うことでシンプルに抑えられるかもしれません。ここはまだ調べきれていないので要検討ですね。

国際化(後悔)

(こちらはapp routerに直接関係しているわけではないですが、公式ドキュメントにはサブパスかドメインによる方法しか推奨されていなかったので、それに従って後悔したパターンとして書いておきます。)

初期には<base-url>/en/~~~のようにサブパスを使用して国際化を実装していましたが、これは明らかに間違いでした。現在は Cookie ベースに移行することで、以下の問題を解決しました。

サブパスを用いた実装の問題点

url: https://example.com/ja/login

  • NextAuth.js との統合問題: NextAuth.js ではapp/api/auth/[...nextauth]/route.tsファイルにてログインページのパスを指定するのですが、サブパスを使用していると、/en/loginのように locale が固定されてしまい、言語設定に関係なく英語のログインページにリダイレクトされてしまいます。
  • Google ログインのリダイレクト URL: こちらも指定する際に locale が固定されてしまうので対応言語を網羅的に書く必要があり、漏れた場合はログインできなくなります。
  • 画面遷移時の問題: パスに locale を含める必要があり、それを page.tsx の props から末端コンポーネントまで props のバケツリレーを行う必要がありました。

url: https://example.com/login
cookie: locale=ja

Cookie を使用することで、パスに locale を含める必要がなくなり、次の利点が得られました:

  • シンプルな URL 構造: パスは/loginのようにシンプルに書けるため、言語を固定せずに済みます。
  • NextAuth.js との統合の改善: locale を意識することなく統合が可能になりました。
  • Google ログインのリダイレクト URL: locale に依存しないため、対応言語を網羅的に書く必要がなくなりました。
  • コードの簡素化: 画面遷移時にパスに locale を含める必要がなくなり、props のバケツリレーも解消されました。

新しい知見というより、元々あったベストプラクティスを知らずに間違った実装をした自分に問題がありました。

スタイリング

スタイリングについては、Mantine と TailwindCSS を併用して実装しています。Mantine が App Router に対応したため、特に問題なく使えています。

まとめと今後の展望

1 年間の経験を通じて、Next.js の App Router は柔軟性のある非常に強力なフレームワークであることがわかりました。それと同時に使う側の力も試されるフレームワークだと感じました(App Router がフレームワークとしてまだ未熟とも言える)。
どのプロジェクトでも知見が足りていないのが現状だと思いますので皆さんの知見を知りたいところです。

フィシルコム

Discussion