Published on

App Routerを本番投入した感想

Authors

前書き

最近本業で App Router をプロダクション環境にリリースしたので感想や設計について書いていく。 App Router がそもそもどのようなものかはこちら

感想

感想としては、コロケーションすることでページ単位のコンポーネントをまとめて置くことができるのでシンプルに作ることができる。 Moving Client Components to the Leaves にもあるが、CC をできるだけツリーの端に置くことは重要だった。 あとは App Router に対応していないライブラリを使用する場合は自分で export し直す必要があるが、これもドキュメントに書かれていた ⇨ Third-party packages

設計

App Router でのフォルダ分割をどうするかについては、コロケーションを重視してフォルダを切った。わかりやすくて良い。 具体的には下記のようにした。

.
└── app/
    ├── great_page/
    │   ├── _components/
    │   │   ├── Form.tsx
    │   │   ├── Form.test.tsx
    │   │   ├── Form.stories.tsx
    │   │   └── ...
    │   ├── _api/
    │   │   ├── getUser.ts
    │   │   └── ....
    │   ├── __types/
    │   │   ├── user.ts
    │   │   └── ...
    │   ├── page.tsx
    │   ├── error.tsx
    │   └── layout.tex
    └── _components/
        ├── Button.tsx
        └── ....

_をフォルダ名の先頭に付けると、ルートとして認識されないのを利用して、ページで使うコンポーネントはページ名のフォルダ内部に集約した。 一方で全体のページで使用されるコンポーネントは app 直下に_componentsというフォルダを作りそこに置いた。

後から知ったが Next.js の中の人もおすすめしていた設計だった。

Great feedback, thank you! It is indeed up to you, the pattern we found useful so far is a _components directory collocated with the route if there are specific components. Otherwise use app/_components. https://twitter.com/timneutkens/status/1659313703374712833

それぞれのルート直下のpage.tsxでコンポーネントを呼び出して、非同期コンポーネントはSuspenceでラップして fallback を表示させるという風にした。

データフェッチに関しては、Hooks などは経由せずにコンポーネント内部でそのままコンポーネントが必要なデータを取得するようにし、コロケーションをここでも実行した。 と言っても公式ドキュメントにもある通り。

import Albums from './albums'

async function getArtist(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}`)
  return res.json()
}

async function getArtistAlbums(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}/albums`)
  return res.json()
}

export default async function Page({ params: { username } }: { params: { username: string } }) {
  // Initiate both requests in parallel
  const artistData = getArtist(username)
  const albumsData = getArtistAlbums(username)

  // Wait for the promises to resolve
  const [artist, albums] = await Promise.all([artistData, albumsData])

  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  )
}
// https://nextjs.org/docs/app/building-your-application/data-fetching/fetching#parallel-data-fetching

余談

初めて Next.js を触る人とペアプロした際に、わかりやすいですねと言っていて、自分からすると Page Routes からだいぶ複雑になったと思っていたが メンタルモデルは意外と単純になったのかもしれないと思った。