【Next.js14 AppRouter】無駄遣い削減アプリ「ちりつも」をリプレイス

こんにちは。Naotoです。

今回は、以前T3 Stackで個人開発した無駄遣い削減アプリ「ちりつも」をリプレイスしたことについて記事にしました。

以前T3 Stackで開発した際の記事は以下です。
（アプリの機能や開発したきっかけなどは、以下記事をご覧ください。）

https://qiita.com/Naoto_eng_7010/items/6b15d6c9f2b80ede6c7c

## リプレイスした理由
今回リプレイスをした理由はNext.js14（AppRouter）に対する理解が、自分の中で曖昧であると感じたためです。

プログラミングを学ぶ上では、
"インプットよりアウトプットに時間をかけるべき"
とよく言われますが、逆に、自分はその言葉に甘えてしまい、アウトプットの方が楽しいが故に、インプットを疎かにしていました。

T3 Stackで開発した際も、そもそも AppRouterで何ができるようになったのか、どういった考え方・メンタルモデルのフレームワークなのか、などを理解しないまま、「動けば良い」という付け焼き刃的な技術で、開発を進めてしまいました。

そのため、「分かっているようで本当は分かっていない」というモヤモヤが自分の中にありました。

そこで、今回、AppRouterについて基礎から学習をし直した上で、基本に徹した方法で再度「ちりつも」を開発し直そうと思い、リプレイスに踏み切りました。

## 開発環境と使用技術
### 開発環境
OS：macOS
IDE：Visual Studio Code

### 使用技術
フレームワーク：Next.js v.14 AppRouter (React v.18)
言語：TypeScript
スタイル：Tailwind CSS, React Icons
認証：Next.Auth
ORM：Prisma
DB：PostgreSQL(Supabase)
パッケージ管理：npm
ソースコード管理：GitHub
テスト：Vitest, React Testing Library, Playwright
その他：zod, canvas-confetti, use-action-state-compat

※T3 Stackで開発した際に使用していたtRPCは使用しなくなりました。

## T3 Stackを辞めた理由
### クライアントとサーバー間のやり取りをServerActionsにしたかったから
T3 Stackとは、Next.js, TailwindCSS, TypeScript, Prisma, tRPC, Next.Authを使用した開発手法のことを言い、クライアントとサーバー間のやり取りは、tRPCを用いて行われます。

また、tRPCとは、フロント・バックエンドともにTypeScriptで開発している場合にのみ使用でき、バックエンドの処理をフロントエンドから関数のように（＋型安全に）呼び出すことを可能にする技術です。

https://trpc.io/

しかし、ServerActionsが登場したことで、tRPCと同様なことをより簡単に実現できると感じ、現時点でtRPCを使うメリットはあまりないと感じました。
（tRPCは設定がめんどくさいがServerActionsは設定がいらない。）

さらに、サードパーティライブラリを扱う前に、まずはNext.jsそのものについて理解する必要があると思った＋公式としてもServerActionsの使用を推奨しているという点も踏まえ、今回は基礎に則り、tRPC(T3 Stack)ではなくServerActonsを使用することとしました。

## Next.js14（App Router）の学習方法
基礎を学習する上で以下の教材・動画を使用しました。

### Next.jsの公式ドキュメント

https://nextjs.org/learn?utm_source=next-site&utm_medium=homepage-cta&utm_campaign=home

Next.jsの公式ドキュメントです。
「Learn Next.js」という学習用コンテンツがあり、基本的な実装方法を１通り学習することができます。

その他、通常のドキュメントも充実しています。

### Zenn本 「Next.jsの考え方」

https://zenn.dev/akfm/books/nextjs-basic-principle

akfm_satoさんという方が書かれたZenn本です。
AppRouterについて、初心者〜中級者向けに、体系的にまとめられており、非常に勉強になりました。

### ShinCodeさんの動画

https://youtu.be/Ca1h3KUfQ5k?si=J5A9tsKQHNTT-CoP

Next.js AppRouterのベストプラクティスについてまとめられた動画です。
上記公式ドキュメントやakfm_satoさんの「Next.jsの考え方」をもとに作成されており、重要な部分がまとまっています。

## リプレイスにあたって意識した点

上記にも記載しましたが、"AppRouterの基礎に則った開発をする"という点を一番意識ました。

その中でも特に意識した点を３つ紹介します。

### 可能な限りServerComponentsを使用すること
できる限りClientComponentsを使用しないようにしました。
例えば、以下のような画面では、フォームの部分（input×2とbutton部分）のみを切り離し、ClientComponentsとして実装するようにしました。

また、AppRouterでは、ClientComponents配下のComponentは全てClientComponentsになってしまうため、できるだけ末端のComponentsをClientComponentsにするよう意識しました。
（Compositionパターンを使用するという方法でも良いかと思います。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817219/ea014ce1-cd23-6942-9891-a875b3239cb3.png)


### 4つキャッシュを理解した上で実装すること
AppRouterには以下４つのキャッシュが存在します。
- RequestMemoization
- DataCache
- FullRouteCache
- RouterCache

これらのキャッシュはパフォーマンスや画面更新等に大きく関わってくるため、しっかり理解する必要があります。

私的にはAppRouterを学ぶ中でキャッシュについての理解が一番難しく感じたため、Qiitaの記事にまとめながら理解を深めました。
個々のキャッシュの説明は以下記事をご覧ください。

https://qiita.com/Naoto_eng_7010/items/efa174517fddd84f9ce3

キャッシュについて最も意識したことは、RequestMemoizationを利用したデータフェッチのコロケーションです。

データフェッチをできるだけそのデータを使用するComponents内で行うことで、Componentの独立性を高めつつ（propsバケツリレー防止）、その一方でリクエストが重複してしまう部分はRequestMemoizationで回避する、という方法をとりました。
そうすることで、Componentsの独立性とパフォーマンスの両方を保てるようにしました。

例）以下の場合、`page.tsx`でなく、`List.tsx`でデータ取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817219/0ccc1e80-6b8f-5d05-55b5-ddee9747a158.png)


また、今回は、API Handlerは使わず、ServerActions内で直接Prisma関数を呼び出したため、ReactCacheを用いてRequestMemoizationと同等のメモ化を実現しました。
なので、厳密にいうとRequestMemoizationは使用していません。
（通常`fetch()`を用いたデータ取得でしかRequestMemoizationは機能しないため。）
```typescript
// app/lib/commonFunction.ts（RequestMemoizationを機能させるためにデータ取得層にて共通化）
// 欲しいものリストの取得（ReactCahceを使用）
export const getWantedItemList = cache(async (userId: string) => {
  return prisma.wantedItem.findMany({
    where: { userId },
    orderBy: { createdAt: "desc" },
  });
});
v
```

※キャッシュの話とはズレますが、データフェッチはできる限り並行で行う（`PromiseAll()`の使用 or コンポーネントの兄弟分割）ことも意識しました。

### Streamingの使用

「ちりつも」はユーザによる操作（データ更新など）が多いアプリのため、Static RenderingよりもDynamic Renderingのページが多くなってしまいました。

そのためデータ取得時には必ずStreamingを使用しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817219/3cfc1b94-a508-f208-8ba5-11de0b00018f.png)

```jsx
<div>
  <h2 className="mb-4 pl-1 text-xl font-bold text-gray-100 sm:text-2xl">
    欲しい物リスト
  </h2>
  <Suspense fallback={<SkeletonList />}>
    <List />
  </Suspense>
</div>
```

上記のようにSuspenseのfallbackにスケルトンUIを指定し、対応しました。


## まとめ
上記の通り、Next.js14（AppRouter）について学習し直した上で、リプレイスを行いました。

インプット学習から逃げずに、AppRouterについて1から学んだことで、今まで理解が不十分でモヤモヤしていた部分がスッキリし、全体像をはっきりさせることができました。

今後は、アクセシビリティ対応やテスト、CI/CDなども勉強し、取り入れていきたいと考えております。

最後までお読みいただき、ありがとうございました。