こんにちは。Naotoです。
今回は、以前T3 Stackで個人開発した無駄遣い削減アプリ「ちりつも」をリプレイスしたことについて記事にしました。
以前T3 Stackで開発した際の記事は以下です。
(アプリの機能や開発したきっかけなどは、以下記事をご覧ください。)
リプレイスした理由
今回リプレイスをした理由は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で開発している場合にのみ使用でき、バックエンドの処理をフロントエンドから関数のように(+型安全に)呼び出すことを可能にする技術です。
しかし、ServerActionsが登場したことで、tRPCと同様なことをより簡単に実現できると感じ、現時点でtRPCを使うメリットはあまりないと感じました。
(tRPCは設定がめんどくさいがServerActionsは設定がいらない。)
さらに、サードパーティライブラリを扱う前に、まずはNext.jsそのものについて理解する必要があると思った+公式としてもServerActionsの使用を推奨しているという点も踏まえ、今回は基礎に則り、tRPC(T3 Stack)ではなくServerActonsを使用することとしました。
Next.js14(App Router)の学習方法
基礎を学習する上で以下の教材・動画を使用しました。
Next.jsの公式ドキュメント
Next.jsの公式ドキュメントです。
「Learn Next.js」という学習用コンテンツがあり、基本的な実装方法を1通り学習することができます。
その他、通常のドキュメントも充実しています。
Zenn本 「Next.jsの考え方」
akfm_satoさんという方が書かれたZenn本です。
AppRouterについて、初心者〜中級者向けに、体系的にまとめられており、非常に勉強になりました。
ShinCodeさんの動画
Next.js AppRouterのベストプラクティスについてまとめられた動画です。
上記公式ドキュメントやakfm_satoさんの「Next.jsの考え方」をもとに作成されており、重要な部分がまとまっています。
リプレイスにあたって意識した点
上記にも記載しましたが、"AppRouterの基礎に則った開発をする"という点を一番意識ました。
その中でも特に意識した点を3つ紹介します。
可能な限りServerComponentsを使用すること
できる限りClientComponentsを使用しないようにしました。
例えば、以下のような画面では、フォームの部分(input×2とbutton部分)のみを切り離し、ClientComponentsとして実装するようにしました。
また、AppRouterでは、ClientComponents配下のComponentは全てClientComponentsになってしまうため、できるだけ末端のComponentsをClientComponentsにするよう意識しました。
(Compositionパターンを使用するという方法でも良いかと思います。)
4つキャッシュを理解した上で実装すること
AppRouterには以下4つのキャッシュが存在します。
- RequestMemoization
- DataCache
- FullRouteCache
- RouterCache
これらのキャッシュはパフォーマンスや画面更新等に大きく関わってくるため、しっかり理解する必要があります。
私的にはAppRouterを学ぶ中でキャッシュについての理解が一番難しく感じたため、Qiitaの記事にまとめながら理解を深めました。
個々のキャッシュの説明は以下記事をご覧ください。
キャッシュについて最も意識したことは、RequestMemoizationを利用したデータフェッチのコロケーションです。
データフェッチをできるだけそのデータを使用するComponents内で行うことで、Componentの独立性を高めつつ(propsバケツリレー防止)、その一方でリクエストが重複してしまう部分はRequestMemoizationで回避する、という方法をとりました。
そうすることで、Componentsの独立性とパフォーマンスの両方を保てるようにしました。
例)以下の場合、page.tsx
でなく、List.tsx
でデータ取得
また、今回は、API Handlerは使わず、ServerActions内で直接Prisma関数を呼び出したため、ReactCacheを用いてRequestMemoizationを使用しました。
(通常fetch()
を用いたデータ取得でしかRequestMemoizationは機能しないため。)
// 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を使用しました。
<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なども勉強し、取り入れていきたいと考えております。
最後までお読みいただき、ありがとうございました。