Nuxt 3で作るSaaS基盤:1年間のリアルレポート!
株式会社リンクアンドモチベーションでフロントエンドエンジニアをしているシモディです。
弊社では現在、プロダクトシリーズの共通基盤の開発を進めており、フロントエンド基盤の設計・実装を私がテックリードとして推進してきました。
その中で、共通基盤のフロントエンドのフレームワークにはNuxt3を採用しました。
フロントエンド共通基盤の開発開始から1年が経過したため、
本記事では、これまでの取り組みを振り返りとして、Nuxt3のプロジェクトを約1年間使用してきた感想や便利だった点を中心にまとめていきます。
Nuxtとは
Vue.jsをベースにしたオープンソースのフレームワークです。
7年前にメジャーバージョンである1.0.0がリリースされて以来、その勢いは衰えず、現代のフロントエンドフレームワークの代表格の一つとなっています。
特にNuxtの3系でのVue 3対応、TypeScriptサポート、Vite統合によって、その人気に火がついたと感じます。
それ以来、新バージョンが驚くほどの頻度でリリースされており、この手厚いサポートも導入を決めた要因の一つです。
利用可能な機能の詳細については後で触れるため、本章では触れませんが、公式ドキュメントが非常にイケてることもNuxtが魅力的な理由の一つだと思います。
Nuxt3を採用した理由
まずここから言及していきたいと思います。
前提として、弊社の既存プロダクトはフロントエンドがすべてVue.jsで書かれています。
そのため、比較的Vueエンジニアが多いこともあり、特に共通基盤だけReactやNext.jsに変更することは考えなかったです。
また、弊社のプロダクトはSaaSアプリケーションなので、
共通基盤の画面であっても顧客ごとに異なるデータが表示されるため、SPAを採用しています。
技術選定時にはSSRを必要とするページが存在せず、全てのページはCSRによるレンダリングになります。
Nuxtの1つの強みとして、「ユニバーサルレンダリングやハイブリッドレンダリングをはじめとした、レンダリングモードの多様性」が挙げられると思うので、その強みを活かせない点については私も非常に悩みました。
とはいえ、その強みを除いても、
- パフォーマンス
- 開発効率の向上
という2点で、純粋なVueを使うよりも十分な破壊力があると感じたので、
Nuxtを採用することに決めました。なお、上記2点の詳細は記事の後半で取り上げます。
共通基盤での使い方
この章では、実際に弊社の共通基盤アプリケーションでNuxtをどのように活用したのかを具体的に紹介していきます。
各種設定周り
- レンダリング方法
- SPAモードを採用し、完全なクライアントサイドレンダリング(CSR)を実現。
- グローバル設定として
ssr: false
を有効化し、SSRを無効化しています。
- ホスティング環境
- ビルドされた静的ファイルをAmazon S3に配置。
- Amazon CloudFrontを用いて、CDNを通じて高パフォーマンスな配信を実現。
- プログラミング環境
- 全てのファイルでTypeScriptを採用し、型安全性と開発効率を向上。
- Eslint, Prettier, Stylelintを組み合わせてコード品質と一貫性を保っています。
- テストコードにはVitestを活用し、高速かつモダンなテストの実現を支援。
- 状態管理にはPiniaを採用。
- ローカル開発環境
- MSW(Mock Service Worker)により、APIのモックを作成し、バックエンドに依存しない開発を可能に。
- Storybookを活用し、UIコンポーネントのデザインと動作を個別に検証。
- 導入しているNuxtモジュール
- Nuxt Security: セキュリティ強化のためのモジュールで、ヘッダー設定やXSS対策を自動化。
- Nuxt Gtag: Google Analyticsの統合を簡略化し、トラッキング設定を効率化。
- Nuxt Test Utils: ユニットテストやエンドツーエンドテストを容易にする開発ツール。
ディレクトリ構成
基本的にはNuxtのデフォルトフォルダを参考にしつつ、
ドメイン駆動設計を参考にしたフォルダ構成にしています。
-
/.mocks
:モックデータやAPIレスポンスを管理。 -
/.storybook
:Storybookの設定と関連ファイルを管理。 -
/assets
:プロジェクト全体で利用される静的リソースを配置。/images
/locales
/stylesheet
-
/components
-
/model
:個別の画面に紐づくコンポーネント。 -
/section
:セクション単位で構成された複合的なUI要素。 -
/shared
:複数箇所で使用される汎用コンポーネント。
-
/composables
-
/domain
:ドメイン駆動設計(DDD)の中心となるドメイン層を管理。-
model
:ドメインモデルやエンティティを格納。 -
repository
:データアクセスの抽象化レイヤー。
-
-
/infrastructure
:インフラストラクチャ層(APIや外部サービスとの通信)を格納。 -
/layouts
:プロダクト共通で利用するヘッダーやフッターなどをまとめる。 -
/middleware
:ページアクセス時のルートガードやリダイレクトロジック。 /pages
/plugins
-
/public
:faviconやロゴをここで管理。 -
/stores
:Piniaの処理の格納。 /tests
-
/utils
:汎用的なユーティリティ関数やヘルパー関数を管理。
※コンポーネントの関係性はこちら
実際に使ってみたNuxtの感想
そもそも僕は以前Vue2で開発をしていたので、最初はそもそもVue3すげーな笑
と思う気持ちも強かったです。
Vue3の良さを語るだけでも1記事書けるのですが、その思いを抑えてNuxtに振り切って良さを伝えていきます。
ページング管理
文句なしです。
Vue2の頃は各ページのルーティング設定を個別に記述していましたが、Nuxtではpages/
ディレクトリにファイルを置くだけで自動的にルーティングが生成されます。
これだけでもNuxtに変えてよかったなと思いますね。
ナビゲーション処理についてもNuxtLink
使えば自由自在で、アニメーションも簡単に実装できます。
CSR Onlyで利用しているだけでもこれだけ恩恵を感じることがあるので、ハイブリッドレンダリングを使う人等はこの何倍も恩恵を感じるのではないでしょうか?
Auto Import機能
コンポーネントやcomposables
内の関数を明示的にimport
しなくても使えるのは、本当に便利です。 例えば、useFetch
やuseState
、さらにはref
やcomputed
といったComposition APIがすべて明示的なimport
なしで利用できるので、開発効率が格段に上がります。最高です。
共通基盤でもcomponents/shared
フォルダ配下のコンポーネント群は頻繁に使い回すため、Auto Importの恩恵を特に強く感じました。
さらに、この機能の良いところは、Auto Importの対象範囲を細かくカスタマイズできる点です。例えば、弊社ではdomain
フォルダ内のリソースもAuto Importに設定し、効率的に活用していました。
一部の記事では「Auto Importが逆に不便」という声も見かけますが、私自身は不便と感じたことは一度もありませんでした!
プロジェクト設定
大前提として、NuxtではVueインスタンス作るためにnew Vue()
やVue.use()
は自分で書く必要なく、Nuxtが裏側でよしなに処理してくれています。感謝。
また、defineNuxtConfig
が使いやすいことも、Nuxtの魅力の一つだなと感じています。
追加のプラグインやCSS関連の設定もここに数行追加するだけで、ほとんどの場合設定が完了します。
さらに共通基盤の特性上、プロダクトごとの情報(URLやプロダクトIDなど)を持つ必要があったのですが、これもruntimeConfig
に記述することで、どこからでもランタイム設定が参照しやすくなるのが良かったです。
設定を簡潔に記述でき、そのシンプルさがプロジェクト全体の開発効率を大幅に向上させてくれました!
ビルド・立ち上げ周り
Viteがビルトインサポートされており、サーバー起動やHMRは驚くほど早いです。
以前はWebback民だったので、初回のバンドルだけに数十秒待つ必要があった僕としては、もはや革命でした。
自動的にTree Shakingやコードスプリッティングもよしなにやってくれるため、バンドラーの設定に時間を取られることもありません。以前はWebpackのプラグインをいちいち調べてはダウンロードし、設定する必要があったので面倒でした。
SSRは全く使っていませんが、ビルドの速さだけを考えてもNuxtを導入する価値は十分にあると感じます。
ミドルウェア
Nuxtのミドルウェアは、認証やルート遷移時のロジックを整理するのに非常に便利でした。
弊社では認証周りはAuth0を使っているので、関連処理がこのフォルダ内に綺麗にまとまることができたのが良かったです。
多言語対応においても、プロダクトごとに利用可能言語も違うので、その切り替えロジックをミドルウェアに完全に移管できたのは大きなメリットでした。
あとは、SaaS特有の一括処理中の画面での排他制御等もここで行うことで遷移の制限をすることができました。
状態管理
useState
には、かなりお世話になりました。
sectionごとのデータの受け渡しが発生する箇所だと、ref
やreactive
で対応しきれないものがあります。とはいえ、わざわざpinia使うほどでも無いなと言う箇所でuseState
が良いソリューションになりました。
piniaの使い心地も良かったです。
自分はVuexライクなOptions StoreよりもSetup Storeな書き方の方が好きなので、それが実現できたことが嬉しかったです。
TypeScriptサポート
ここもめちゃくちゃ相性良いなと思いました。
一連の型に関しては.nuxt/nuxt.d.ts
に格納されており、関連モジュールの型も充実していました。
「VS Code × Volar」の相性も抜群で、ほとんどは実装中にIDEが指摘をしてくれました。
(※ポケポケユーザーなのですが、「ミューツー×サーナイト」のコンボを見ているようでした。)
データフェッチング
こちらについては正直なところ、あまり使いこなせなかったという印象があります。
api通信周りはすべてfetcher.ts
にまとめ、その関数をRepositoryパターンでラップして利用し、Composableとして使い回す設計を採用していました。
そのため、コンポーネント内でuseFetch
やuseAsyncData
を直接使用するユースケースはほとんどありませんでした。
しかもuseFetch
については、ボタン操作や条件変更時に自動的に通信が走ってしまうので、これが良くも悪くも適さないケースがありました。最終的には$fetch
に統一して利用していました。
※useFetch
とuseAsyncData
の違いはこちらの動画で勉強させていただきました。
周辺エコシステム
全体的にモジュール群は非常に充実しています。
この記事を書く中で色々調べなおしたのですが、使用してみたいものがまだまだ見つかります。
本当に無数にあります。ドキュメントも綺麗です。
その中でもVueUseはやはり便利だなと感じました。(Nuxt専用ではありませんが。)
おかげで、toBならではのファイルアップロード周りの処理は非常に楽でした。
とはいえ、ローカル開発をするにあたって当初はStorybookを採用していたのですが
バージョンを上げたら動かなくなることが度々発生して困った記憶があります。
また、通常のVueと違って、問題が発生した際に調査しようとしても、ネット上の情報がNext関連と比べるとまだ少ないため、その点の調査はしんどかったです。
今後Nuxtユーザーが増えることで、ここら辺のソースが溜まっていくと良いなと思いました。
まとめ
実際に私自身もVueFesに参加し、Nuxtの未来を聞いた上で今後のアップデートが非常に楽しみになっています。
「共通基盤」という変更しづらいアプリケーションだからこそ、保守も頻繁に行って廃れないようにしていきたいと思います。
最後に.....
Nuxt最高!!!!
Discussion