📑
いまNext.jsで新規サービスを立ち上げるときの観点(Router・CSS・認証・監視など/2023年末)
免責事項
- 社内向けに展開するように雑にまとめました
- Next.jsの知見が深くない人がリードしてPoCを立ち上げなきゃいけなくなったが、社内的にはNext.jsを推奨しているみたいな場面を想定しています
- なので自信ないところも多いですが割と断言するように心がけて書いています
- PoCの立ち上げ想定なので、jest/Storybookなど内部品質面についてあまり深く書くことを避けています
- ほぼ自分の知識だけで書いており私見も多いですし、そもそも自分自身がトップクラスの知識や視座を有しているわけでもないので、まずは以下の話を理解はした上で、踏襲するかどうかは別途他記事やGitHub、公式ドキュメントなどを漁って判断することを推奨
App RouterかPages Routerか
- 2023年末現在まだApp Routerは技術記事が足りてきている印象ではないため、社内でノウハウを積極的に貯めていく気概がある、余力がある、中長期的にメンテナンスすることが確定しているならApp Routerを選ぶ
- 実際App Routerは軽く触っただけでもloading.tsx(というかSuspenseによる責務分離)が便利、generateMetadataが便利、Server ComponentとClient Componentを脳内で区別することでむしろ分かりやすくなる、共通レイアウトの組み方が自由度高くなった、エコシステムが中長期的にそちらに向いていく、Server Actionsで近々フォーム開発の常識が変わりそう、などの点も含めて本来は望ましいと思う
- その他、Pages Routerでもパフォーマンスは高くできるが、より高いパフォーマンスを求める場合もApp Router(複数回のフェッチをページ内の各Server ComponentからやったときにStreamできるのがFCPの低下等につながるため)
- Pages Routerをあえて選ぶことも個人的には賛成で、そのときはClient Componentへの切り分け等を将来的にやりやすいこと、Pages Routerにしか依存しないかつグローバルなライブラリを下手に入れないこと(i18n系など。パッと調べた感じではi18nはhttps://next-international.vercel.app/docs が良さそうに見える。少なくとも同じAPIで両方対応できますってやつはBabel等で黒魔術やってそうなので要警戒)、router.eventsがまだnext/navigationで未対応なのでできれば依存しないほうが安全など考慮する
インフラ
- Vercelが最優先。1メンバーあたり
$20
かかるが独自でサーバーを立てた場合にNodeサーバーを月$20以下で安定稼働させる方法は少ない - 自社で持っているクラウドがあり、そこに統合すべき理由(ドメインやIaCなど)があり、かつインフラ担当のリソースを割けるならAWSやGCPに載せても良い
- AWSならApp Runner、Fargateなど、GCPならCloud Runなど
- これらに置くならDocker Standalone Buildでやるのが推奨
- たとえばApp RunnerはNodeランタイムも持っているがLTSに追従してこないのでECRへのPushをHookにデプロイするパイプラインのほうが良い
- 稀にCloudFrontの裏にVercel置いちゃいましたという話も聞くので、冒険できる方はその方針でもいいかも。キャッシュの動作やラウンドホップ数の点で意図しない挙動をしないのかなという懸念があるため。
- ドメイン
- 後述するCookieの可能性等を考慮しても、全部同じドメインのサブドメインで取り回すのが基本的にはおすすめ。example.com、api.example.com、assets.example.com、lp.example.com、mail.example.comなど
- 語呂合わせなどの都合がなければgTLDから選んだほうが安定
- .devと.appみたいに、HTTPSを強制するセキュアなTLDもある
- ドメインの取得は某などを除けばどこでも良いが、社内で取得・管理するレジストラは統一したほうがうっかり手放すなど無くてよい。たとえばAWS使う可能性があるならRoute 53が望ましい
ページ生成
- ISRはちょっとトレンドとして廃れてきている感があり、キャッシュなどこだわるならApp Routerにしようよ感があるのでどのみち選ばないかな
- 静的Exportもメジャーではないので金額が許すならVercelないし別のサーバー等にお金を積んだほうがいい
- メジャーではない方法を選ぶと他ライブラリや文献との相性が悪くなる
CSSおよびUIコンポーネントライブラリ
- Tailwind CSSがエコシステムも充実しており、Babel等のJSエコシステムへの依存がほぼ無く(twin.macroなどを使う場合は別)、create next appでも推奨されているため有力
- あとAIによるコード生成と相性が良いのが個人的に大賛成なところ(https://v0.dev/ とか)。ただ可読性とのトレードオフではある
- ただしTailwind CSSの場合、結局UI構築のフェーズで何らかのUIライブラリが欲しくなるので、そこの選定も追加で必要
- 2023年10月現在置きに行くならRadixかその上に乗っているshadcn/ui
- Headless UIは次点
- daisyUIは面白いけどHTML自体に詳しくなる必要があるなどテクニックが要求されるので、好みが分かれるかも
- なおこの手のライブラリに限らずCSS、UIライブラリ系は基本VS関係にあると思われがちだが、別に物理的には併用可能(reset CSSの衝突には注意)なので、PoCフェーズとかだと特に併用しちゃうのはやってOKだと思う。daisyUIとRedixの併用など普通にできるし。
- Tailwind CSSやる場合は以下考慮
- 初期構築でtailwind.config.jsに基礎的なカラーパレットを決めでいいので置いておく。原則、gray-500みたいな色は使わないほうがいい
- prettierかeslintでクラス名のソートを自動化する
- clsx系列のライブラリを入れる
- tailwind-mergeは個人的にはどちらでもいいかな。UI層のコンポーネントにおけるClassの衝突がどの程度気がかりかによる。そもそもclassnameをPropsで渡さない思想を強めに持っていたらリスクも小さい
- IDEでTailwind CSSの拡張機能を入れる
- 好みによるが、独自Prefixを付ける設定をすると、AIによる自動生成と相性が悪くなる可能性が高いので、多少CSS RulesのPurgeの効きが悪くなることを妥協してもPrefix付けないほうが良いかなと思っている
- ニッチ過ぎる話だけどresolveConfigはBundle Sizeが10KBくらい増えるので個人的非推奨
- 最初にGitHubのawesome-tailwindを見てエコシステムに詳しくなっておくといいかも
- SaaSなど、Bundle Sizeにそこまでセンシティブではない場合や、Server Componentへの対応に苦労することになってもよい(今後の展開によっては苦労しないこともあるかもしれない)という場合は、ChakraやMUI、emotionやstyled-componentsなどを選ぶ。もっとも、どのみち末端のUIコンポーネントはApp RouterにしてもClient Componentにすると思うのでさして問題ではない気もする(これはTailwind CSSにRadixなどを合わせた場合も同じ)が、Layout ComponentはServer Componentになりがちなので将来的にServer Componentを狙うなら利用するコンポーネントは考えたほうがいいかも
- 話は変わるけどTailwind CSSベースのLayout ComponentだけのLightweightなライブラリがほしい
- Kuma UIなどZero Runtime CSS in JSで、JS内にCSSを書いていけるライブラリも出てきているので、自力でドキュメントを見たりアップデート情報を追えるという方はその辺を選んでもいいかも
- linariaはここ数年でNextのサポートが弱くなっていそうなのであまり推奨しない
アーキテクチャ
- pagesに直接色々書いてしまうと後々App Routerに移行するのが大変になるので、pages以下のコンポーネントにはHeadだけ置いて、あとはcomponents/pages以下などに逃がすのがおすすめ。そうしておくとあとからcomponents/pages以下を全部use clientすることで無理やり移行を完了できるオプションが生まれるので
- bulletproof-reactは基礎的なリテラシというか価値観を掴むのにはうってつけなのでリードすることになったら目を通しておくとよい(自分の記事のステマ)
- コンポーネントのディレクトリ構成は、派閥としてはLayer派とFeature派に分かれる
- Layer派は、atom molecule organismや、ui / parts / layoutsといった技術都合でディレクトリを分ける
- Feature派は、authとかrestaurantとかpaymentといった機能都合でディレクトリを分ける
- これらの特徴から逆算すると、Layer派は、アプリケーション全体でデザインシステムなどコンポーネントの責務を分けつつ共通化することを重視している場合に有用と考えられる。Feature派は、アプリケーション内に色々な種類の機能が増えがちなときに使える。シンプルなツールやメディアであればLayer、自社サービスなどで何かと色々な種類の機能が増えるならFeatureではないかと思う
- もちろんFeatureにしていても、commonなComponentを置くためのディレクトリは切れるし、逆も又しかりなので、あくまで大枠の方針の話
便利コンポーネントやUtils
- App Routerになるとどのみちクビになるが、Head.tsxコンポーネントを作るのはおすすめ。next/headのラッパー
- next/routerのラッパーも作る。これもApp Router移行時にnext/navigationに移行しやすくするため
- next/linkやnext/imageも変更が多いのでラッパーを作っておくと安心安全
- 初期にラッパーをたくさん作って、ESLintで依存関係を縛っておくくらいは、工数も掛からないしおすすめ
- ラッパーをただ作るのではなくInterfaceを分離することが重要なので留意すること
- バックエンドをREST APIにしていて、開発工数をケチりたいときは相変わらずaspida(+useAspidaSWR)がおすすめ
- APIのレスポンスの型定義を固めるためになんちゃってRepository層を置かずに済むため
- なおpathpidaはTyped Routesが来ると不要になるので今は際どいタイミングかも
- aspida知らない場合はtRPCなどのソリューションでも良いと思う
- GraphQLエコシステムは詳しくないので割愛
- 社内管理画面を作るその前に、microCMS等のHeadless CMSでどうにかできないか考える。microCMSを使うなら公式SDKを推奨します
- 全体的な心構えとして、認証ライブラリとかUIライブラリ問わず、必要らしいからとりあえず入れるというスタンスだと無駄になることが多いので、本当に必要なのか、使わなくても実装できる道筋が見えているうえで、何か理由があって入れるという考えで使うのが理想。Mustで入れないといけないライブラリなんて実際はほとんどないので(わかりやすい例だとaxiosすらなんとなくデファクトだったけど今となってはfetchでだいたいOKとなっている、など)
認証
- 大枠の話
- JWT(+ Authorization Header)で認証するかCookieで認証するか
- Server Component内で認証が必要なAPIを叩くことはあり得ると思うので、そのときCookieベースでないとサーバー側に認証に必要なトークンを渡す方法が無い(多分)ので、その条件を狙うならCookieベースが良い
- たとえばFirebaseで認証しているならCookieを採用するのが望ましい。だがバックエンドの実装が面倒ではあるので、開発するメンバーの練度によるかも
- ちょっとフロントエンドの話から外れるが、Cookieをバックエンドで発行するなら考慮することの一例として、ExpiredをAPIアクセスのためにN日後に更新したほうがいいのか、それとも一度発行したら二度と変えないのかによって、ユーザーに再ログインを要求する頻度が変わる。このへん割とサービスによって違う印象があるが何をもって決めるのか、またどう実装するのが綺麗なのか。その他Cookieには各種属性があってそれぞれ重要なので他サービスを見たり誰かのレビューをもらうなどして固めるほうがいい
- 割り切って認証が必要なAPIは全部Clientから叩くってしても、そもそもSEO的な話にはだいたい関係ないし大丈夫とは思うが認知コストは高い
- たとえばPublicな一覧画面で、各カードにブックマークボタンがあって、アクセスしたユーザーがブックマーク済みなら予めチェックを入れて表示みたいな要件があったら、一覧画面はSEO的にはServer ComponentでFetchしたいのにブックマーク済みかどうかは認証を持たないといけないのでClient Fetchしたいとなり、全部Clientに寄せるか、あとからClient Fetchした結果を当て込むかみたいなことになり悲しみが生まれる。逆に学習コストが高い気もする。こういうときServer Componentで認証できるようにCookieベースが嬉しいよねという話
- こういう話を聞いて何をいっているか分からなかったら尚更Cookieにしたほうがよさそう(※繰り返しだがSEOだったりLCPを重視しているときだけの話ですが)
- というところまで想像したらCookieに倒すのをちょっと最初頑張って実現したほうが良いなとは思う
- もちろんCookieにするならサービス全体を通して基底のドメインが共通であることが前提なのでそのへんに悪い都合があったら無理
- 認証について考えるときの観点
- 何ログインに対応するか(推奨はGoogleログインのみだが、メールアドレス+パスワード、SNSなど多岐にわたる。少なければ少ないほどいい)
- ユーザー情報は何が持つのか(自社DBなのか、FirebaseやCognitoなどのPaaSなのか)
- ユーザー識別のためのトークンはどこが発行するのか(バックエンドなのか、PaaSなのか)
- トークンをクライアントは何に持つのか(JWTをStorageに持つのか、Cookieなのか)
- 以上を連携させるためのログイン・ログアウトの実装の責務は誰が持つのか(バックエンドなのか、PaaS+フロントエンドなのか、PaaS+バックエンドなのか)
- ログイン・ログアウトをまるっとささえるフレームワークを使うかどうか(LaravelやRails等のバックエンドフレームワークに頼るか、NextAuthやRefineなども組み合わせるか、BlitzやfrourioなどNext.jsごと囲い込んでいるフレームワークを選ぶか)
- これらを自身がこれまで経験してきたサービスはどうしていたか把握したうえで、新規開発するサービスではどこを変えるのか、それとも変えないのか判断し、不確実性の高いところから潰していく開発手順が一例
- JWT(+ Authorization Header)で認証するかCookieで認証するか
監視
- インフラによるが、Sentryだけはどのインフラに載せることにしてもオススメ
- PoCでもエラー調査に時間を食わされるのはもったいないので入れちゃうほうが好き
- Bundle Sizeが大きいのが嫌な点だが、定期的に競合を調べても正直Sentryが一番対応Platformも多く、SDKも充実しており、長期的に仲良くできる印象がある
- @next/sentryを入れてDocument通りにセットアップする
- フロントは無駄なエラーをキャッチしがちなので、リリース当初は監視して無駄なやつはIgnoreErrorsで無視することでQuotaを節約すること
- 簡単にやるならSlackへのアラートまで設定しておくといい
社内向けの雑なまとめではありますが、今自分が認識している情報や知見を色々とまとめてみました。参考になれば幸いです!
最後まで読んでいただきありがとうございました!記事が参考になったらバッジお願いします!
オンライン家庭教師マナリンクを運営するスタートアップNoSchoolのテックブログです。 manalink.jp/ 創業以来年次200%前後で売上成長しつつ、技術面・組織面での課題に日々向き合っています。 カジュアル面談はこちら! forms.gle/fGAk3vDqKv4Dg2MN7
Discussion
私も今 App Router を使って開発をしているので面白い知見を得ることが出来ました
ありがとうございます
App Router 関連で1つ質問です
現状 App Router では msw は使用不可ですが、 API のモックはどうされていますか?
私は今まで msw を使用していましたが、App Router と msw や Nock のようなネットワークリクエストをインターセプトするライブラリは相性が悪いです
msw, Nockが使えない現状で、API のモックはどのようにされていますか?
コメントありがとうございます!
申し訳ないのですが、App RouterでAPIモックを実装した経験がないので、回答ができません。
というのも、いま本業(マナリンク)のNext.js製アプリケーションはAPIモックを設定済みなのですがApp Routerに移行しておらずPages Routerのままです。
個人開発で2年ほど開発しているサービスもあり、そっちはApp Routerなのですが、個人開発ということもあってAPIモックなどは整えていないという感じです。
なので、自分としても気になるトピックです!もし情報が入手できたらコメントします!
v13.4.16からmswをいい感じにモックしてくれる機能が追加されています。(Experimentalですが)
おお!ありがとうございます!全く知らなかったので有り難いです。next.onFetchというAPI、分かりやすくていいですね。こういうところで公式サポートが優先されるPlaywright強いなと思いました。