Next.js13のapp directoryでチケット共有アプリを作ってみた
はじめに
今回GWという時間を利用して、Katacky
というチケット共有アプリを開発しました。
アプリ概要
ソースはこちら
家族やカップル、友達の間で使えるオリジナルチケットを作成、譲渡、使用できるWebアプリです。
スケジュール設定をすることであるユーザーに月に2枚チケットを配布するみたいなこともできます。
サービス名のKatacky
というのはこういう身内だけで運用するチケットといえば、肩叩き券だなと思ったのでそこから考えました。
ちなみにKatacky
のロゴはBRANDMARKというサイトで作成したものです。
こんな感じのチケットが作れます
なぜ作ったのか
彼女とダイエットに勤しんでいまして、ただ月に何回かはチートデイ的に1食好きなものを食べる権利をお互い持っているのですが(彼女は2回、私は1回)、この権利を行使したことをDiscordに記録していました。
これアプリ化したいなーと思っていたので今回作ってみました。
技術スタック
フロントエンド + APIサーバー
アプリを作るにあたって、フレームワーク・UIライブラリを何にするかは悩みましたが、今回はNext.js
13で登場したapp directory
を使ってみたいなと思ったのでNext.js
に決定しました。
(Remix
やNuxt
、Solid.js
、SvelteKit
あたりが他の候補でした)
ちょうどGW中にGAになりましたね。
またAPIサーバーとフロントのインターフェースにはtRPCを採用しています。
2023/05/28追記
Next.jsのServer Actionが発表されたことを受けて、Query系のAPIはServerComponent内で呼出し、Mutation系はServerActionを使用することで、tRPCを導入する理由の一つだったフロントエンドのコードベースの中からシームレスにバックエンドAPIを呼び出すという要件を満たせるようになったので現在はtRPCは使用していません。
スタイリング・UIフレームワーク
普段は個人的にemotion
やstyled-components
といったCSS in JS系のライブラリを使うことが多いのですが、今回app directory(React Server Component)
を使うということでこれらのライブラリは相性が悪いため、初めて巷で話題のTailwindCSS
に触れてみることにしました。
またUIフレームワークとしてshadcn/uiというフレームワークを採用しています。
これはRadix UI
というHeadlessなUIフレームワークとTailwindCSS
を組み合わせたもので、カスタマイズが容易そうだったので採用しました。
Mantineも良さそうだったんですが、emotion
依存が気になったので今回は見送りました。
(ただMantine
は今後emotion
依存をやめる可能性があるようです: 参考)
認証
認証はFirebase Auth
のメール+パスワード認証を採用しました。(今後他のプロバイダーも追加していく予定です)
またNext.jsと合わせて簡単に認証情報を利用するためにAuth.js(NextAuth.js)
も採用しています。
ホスティング
ホスティング先はNext.js
との相性の良さからVercel
にしました。
Firebase
を使っているので、Firebase Hostingもありかなと思ったんですが、Next.jsのホスティング先としてはまだまだ安定版ではないようなので今回は見送りました。
データベース
今回はPlanetScaleを採用しています。
Firebaseに依存しているからFireStore
もありかなと思ったんですが、個人的にはRDB
の方が好みなので今回は採用を見送りました。
ちなみに開発当初はFirebaseではなく、Supabaseを使っていて、DBもSupabase
のPostgreSQL
を使用していたのですが、後述するFirebase Cloud Messaging
が使いたくなった関係でSupabase
依存をやめ、PlanetScale
へ移行することになりました(Authも合わせて移行しました)。
技術選定の前にちゃんと要件固めとかないと。。。
またORマッパーとしてPrismaを採用しています。
結果的にT3Stackと呼ばれる構成になりました。
こだわりポイント
グループ登録をQRコードでできるようにした
このアプリではグループという概念があり、ユーザーは同じグループのユーザーにのみチケットを譲渡できるようになっています。
このグループはユーザーが自由に作成できるのですが、他のユーザーが作ったグループにどうやって参加できるようにするか悩みました。
メールアドレスを入力させて招待メールを送る動線も考えたのですが、アプリの想定用途的にも家族やカップルなど直接顔を合わせるようなメンバーでグループを作ることが考えられるので、この動線ではユーザーにとって少しめんどくさいだろうなと考えました。
LINEの友達登録のようなQRコードでの登録の方が今回のアプリ的にはベターだと考えられるので、今回はこちらを採用しました。
QRコードの読み取りはjsqr、作成・表示はnext-qrcodeを使用しています。
チケットの受け取りをプッシュ通知するようにした
Katacky
ではチケット受取時のプッシュ通知に対応しています。
これまでWebのプッシュ通知はiOSでは対応していなかったのですが、ついにiOS 16.4でSafariでもプッシュ通知が利用できるようになったこともあって実装にチャレンジしました。
プッシュ通知の送信はFirebase Cloud Messagingを使用して行っています。
PWA対応
プッシュ通知を実現したかったこともあり、PWA
に対応しています。
ぜひホーム画面に追加
して使ってみてください!
ちゃんとアイコンも表示されます
チケットをスケジュール発行できるようにした
私と彼女の要件だと月に1回チケットを私は1枚、彼女は2枚発行する必要があるのですが、毎月手作業でやるのは面倒くさいと思ったのでスケジューリング機能をつけて自動化しました。
スケジューリングはVercel
を使っていることもあり、Vercel Cronを採用してみました。
実行したいGET API(今回はRoute Handlerを使用しています)とvercel.json
を用意し、以下のように記載することで簡単にスケジューリングできました。
{
"crons": [
{
"path": "/api/ticket/schedule",
"schedule": "0 20 * * *" // 毎日AM5:00に↑のAPIが呼び出される(Vercel CronはUTC基準なので注意)
}
]
}
ただ↓にあるように
While in beta, Vercel Cron Jobs are free on all plans. However, it'll be a paid feature for general availability.
とのことなので、ベータが外れた暁には無料で使えなくなる可能性が高そうですね。
開発してみて感じたこと
app directory(Next.js)
app directoryについての詳しい説明は他に素晴らしい記事がいくらでもあるので割愛します。
とりあえず公式ドキュメントに一通り目を通して、あとはトライアンドエラーの精神で頑張ろうと意気込みました。
正直まだまだちゃんと使いこなしているとはいえず、どういう設計がうまく合うのかはこれからも模索していく必要がありそうです。
サーバーサイドのキャッシュ戦略がまだよくわかっていないところもあるのでちゃんとキャッチアップしたいですね。
Server Component内で非同期処理をasync-awaitで書けるのは素晴らしい
こんな感じで書けて非常に直観的。個人的にはgetServerSideProps
の中で書くよりもぱっと見でやりたいことがわかりやすいです。
export const Profile = async () => {
const user = await getUserInfo();
if (!user) {
return null;
}
...
状態管理やデータキャッシュ周りはクライアントからサーバーサイドに寄せるような構成が主流になりそうな雰囲気
基本的にAPIからのデータのfetchもServerComponent内でやることが多く、ReactQuery
やSWR
でやっていたようなクライアントでのデータキャッシュみたいな機構がそこまで必要じゃなくなって来るのかなと感じました。
今回ぐらいのシンプルなアプリだと管理したいようなステートもそこまで多くなく、今回は特にクライアント側の状態管理系のライブラリは使用していません。
バグのような挙動もいくつか見られた
ベータ版(GW中にGAになりましたが)ということもあり、ところどころ動作が不安定になることがあります。
謎にエラーが発生したと思いきや、何もせずにもう一度ページを開くと直ったり、実装が悪いのか何かしらのバグなのかわからない状態で開発を進めるシーンが何回かありました。
ただこれは今後改善されていくはずなので安定するのを期待して待ちたいところです。
いくつか気になった事象は後述しています。
周辺ライブラリがまだ対応できていないことが多い
これも前述した通りまだまだ出たばかりなので仕方のないことですが、周辺ライブラリが対応できないことが多く、公式のドキュメント通りに実装してもうまく動かないことが何回かありました。
例えばtRPCは公式のインテグレーションガイド通りに進めると、クライアントからのAPI呼び出しでエラーが起きました。
根本的な原因はわからないですが、今回はcreateNextApiHandler
を使うのでなく、以下のようにfetchRequestHandler
を使うことでなんとか動くようになりました。
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "~/servers";
const handler = (request: Request) => {
return fetchRequestHandler({
endpoint: "/api/trpc",
router: appRouter,
...
});
};
export const GET = handler;
export const POST = handler;
TailwindCSS
開発前には個人的にはclass
属性がやたら長くなることと、実際のCSSのプロパティとTailwindCSS
で設定するクラス名に結構ギャップがあって都度調べないといけないところが気になっていました。
ただ実際に開発で使ってみると見えていなかったいいところもありました。
簡単に所感
・React
のようなコンポーネント単位でスタイルが閉じるような環境ではclass
属性の長さはあまり気にならない
・VSCodeの拡張機能である程度補完は効く(なぜかたまに効かないことがある?)
・ダークモード対応が楽!
・今回あまり使いこなせていないが、デザインシステムをかっちり構築したいようなアプリではかなり重宝しそう
開発中遭遇したエラーや問題
開発中遭遇したエラーや問題をいくつか書いておきます。
同じ事象に遭遇した方の助けになれば幸いです。
You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
何回も見たエラーです。
サーバーコンポーネントでクライアントコンポーネントでしか使えないモジュールをimportした時に発生します。
特に考えさせられたのはutilファイルみたいなものを作成しようとしたとき。
個人的にはhooksと純粋な関数や定数みたいな分け方より、utils/form.ts
みたいな機能ベースでファイルを作ることが多いんですが、こうするとサーバーコンポーネントでも使えるモジュールとクライアントコンポーネントでしか使えないモジュール(hooksなど)が混在してしまい、よくこのエラーで怒られていました。
共通モジュールはクライアントでしか使えないものとそれ以外でファイルベースで分離することが求められそうです
これに限らず今までのやり方をそのまま踏襲すると問題が起きるので、app dirの気持ちになることが大事ですね。。。
unhandledRejection: Error [FirebaseError]: Messaging: This browser doesn't support the API's required to use the Firebase SDK. (messaging/unsupported-browser).
Firebase Cloud Messaging関連のモジュール実装時に発生した問題です。
サーバーサイドでのレンダリング時にFirebase Cloud Messagingが要求するAPIがブラウザにないぞと怒られています(当然ですね)。
isSupportedという関数が用意されているのでこれを使用して回避します。
戻り値がPromiseなので注意。
@supabase/auth-helpersのcreateRouteHandlerSupabaseClientでセッションが取得できない
これはQiitaの方で書きました
が、結局supabaseはやめてfirebaseに切り替えました。TypedRouteが正しいパスを指定してもエラーになる
Next.js@13.2でTyped Link
がベータ提供されるようになったので嬉々として利用していたんですが、たまに正しいパスを指定しているのにTypeScriptエラーが出ることがありました。
この場合はVSCodeのTypeScriptサーバーを再起動すると直りました。まだベータだからなのか私の環境に問題があるのかは不明です。
機能としては素晴らしいので今後のアップデートに期待です。
おわりに
app directory
は個人的には総合的に見れば開発体験上がった感覚ですし、GAになったことで今後さらに重要になっていくことでしょう。
最近のアップデートを見るに力の入れようが尋常じゃないように見えるので今後どんな機能がリリースされるのか楽しみですね。
Katacky
の開発に関しては、まだいくつか見えてるバグはあるんですが、ゴールデンウィークの短い期間での実装でなんとか形にできてよかったです(WakaTime
によると大体40時間くらいで実装できたみたいです)。
この後は時間見つけてバグfixや機能追加、あと前から使ってみたかったAstroでLP作ってみたりしていきたいです。
なんとかNext.js側の今後のアップデートにもついていきたいところです。
Katacky
、よければ使ってみてください!
Discussion