最近立ち上げたプロジェクトで採用したフロントエンド技術スタック
はじめに
こちらは e-dash Advent Calendar 2024 の17日目の記事です。
はじめまして、e-dashのフロントエンドエンジニア、kyonsiと申します。
クリスマスシーズン、皆さんいかがお過ごしでしょうか? 私は、夜に輝くイルミネーションを眺めながら、「このキラキラのアニメーション、durationは何ミリ秒なんだろう? CSSで再現できないかな?」なんて、ついフロントエンドエンジニア的な思考が巡っています。
本記事では、最近立ち上げたフロントエンドプロジェクトで採用した技術と、その選定理由を軽くご紹介いたします。「ちょっと攻めすぎじゃない?」「いや、もっと枯れた技術でいいでしょ?」といった社内議論も、クリスマスムードに包まれればスパイス程度。その過程こそが、フロントエンド開発の醍醐味といえるでしょう。
それでは、ホリデームード漂う中、スノードームのようにキラキラと舞う技術の世界を、一緒に覗いていきましょう。
フレームワーク選定
Next.js
フレームワークはNext.jsを採用しました。理由としては、私が好きという点もあるのですが、以下が挙げられます。
- 社内に経験豊富なメンバーが多い
- 複数のレンダリング方法(SSR、SSG、ISRなど)への容易な対応
- Reactの最新機能(Server Components、
react.cache
)が利用可能
他の選択肢としてはRemix(React Router v7)もありますが、RSCに対応していなかったため、今回は見送りました。今後は最近社内で最近Remixを採用したプロダクトがあるので、評判が良ければ、ユーザー操作が少ない管理画面や社内ツールで試行する可能性はあります。
App Router or Pages Router
Pages Routerでも特に大きな問題はなかったのですが、将来的な技術的負債を考慮し、App Routerを採用することにしました。また、社内でのApp Router利用実績があり、問題発生時に相談できる体制があることも後押しとなりました。
RSC(React Server Components)がサポートされていることで、以下のような明確な役割分担が可能になります。
- Server Component: データ取得や副作用のない処理
- Client Component: 状態管理やユーザーインタラクション、データ操作など副作用を伴う処理
これにより、コンポーネント間の境界が明確になり、設計しやすくなると考えています。従来はルートコンポーネントで全てのデータを取得していましたが、各コンポーネントが必要なデータを自律的に取得できるため、データ依存関係がないコンポーネント同士は並列でデータ取得やレンダリングが可能になります。その結果、パフォーマンス向上も期待できます。
UIライブラリ
e-dash ui
社内で開発したTailwind CSSベースのUIライブラリ「e-dash ui」を採用しています。Design TokenやTailwindの設定を通して一貫性のあるデザインシステムを実現しており、共通コンポーネントの提供によって開発効率も大幅に向上しています。もともとは複数のプロダクトに散在していたコンポーネントをフロントエンドエンジニアの提案でパッケージ化したもので、今回のプロダクトでも導入することで、実装工数を大きく削減できました。
CSS
Tailwind CSS
e-dash uiでTailwind CSSが採用されているため、本プロジェクトでもTailwind CSSを導入しています。
そのまま導入すると秩序が乱れがちなため、eslint-plugin-tailwindcss
で未定義ルールの検知やクラス名のソートを行っています。また、Tailwind VariantsでVariantごとのスタイル定義を管理しやすくしています。ちなみに私はもともとマークアップエンジニアとしてCSSを死ぬほど書いてきたので、CSS Modulesで書くのも好きです。
エコシステム
ESLint & Prettier
LinterとFormatterは必須と考えており、ESLintとPrettierを導入しました。チーム内にESLintのエキスパートがおり、素晴らしいプリセットが整備されている点が大きな理由です。
次点でBiomeやoxlintなどの選択肢もありますが、まだ対応ルールが少ないのではと考え見送りました。(実際に比較したわけではありませんが……)
ESLint v9でFlatConfigに移行する必要があるため、その際にBiomeやoxlintを改めて検証してみようと思います。
特に気に入っているルールは、疑似的にpackage-privateを実現できるeslint-plugin-import-access
です。
Storybook
コンポーネントカタログにはStorybookを採用しています。基本的にすべてのページコンポーネント、共通コンポーネントにStoryを作成するルールとしており、これは全社的な取り組みでもあります。
StorybookのBuilderはwebpackベースのNext.js設定を利用していますが、モジュールが増えるにつれてビルド速度の遅さが気になってきます。最近、Viteベースの実装がexperimentalで登場したので、どこかで検証し、早ければ移行を検討したいと考えています。
ちなみに今回はTest Runnerは利用していません。Playwrightでテストできるメリットはありますが、実行速度が遅く、Flakyになった場合のメンテコストを鑑み、導入を見送りました。
NPM
パッケージマネージャーにはNode.js標準のnpmを使用し、Corepackでバージョンを制限しています。モノリポ構成でnpm workspacesを活用し、共有するローカルパッケージを管理しています。また、並行化とキャッシング目的でTurborepoも導入し、リモートキャッシングは使用していません。
開発を進めるにつれ、インストール速度や厳格なパッケージ管理が魅力的なpnpmが良いという声もチーム内で出ているため、将来的に移行する可能性もあります。
Vitest
テストランナーにはVitestを採用しています。ESM対応と高速な実行が魅力で、ESM非対応のJestを選ぶ理由は現時点で見当たりません。testing-library/react
を使用してjsdom
またはhappy-dom
環境でコンポーネントのテストをしています。
最近experimentalで実装されたBrowser Mode
はまだ試していません。
Playwright
E2EテストにはPlaywrightを採用しました。E2Eテストフレームワークのデファクトであり、社内のQAチームがPlaywrightに精通しているため、QAチームがリグレッションテストを書くためのプラットフォームとして導入しています。
Chromatic
ビジュアルリグレッションテスト(VRT)とデザインレビューのためにChromaticを導入しています。StorybookホスティングからVRT、デザインレビューまで一元的に行えるのが利点です。Storybookさえちゃんと書かれていればVRTが自動で行われるのでかなり嬉しいです。
Chromaticは結構高いと聞くのですが、TurboSnapのおかげか費用面の問題は今のところ発生してません。今後コンポーネント数が増えてきて費用の問題が発生したら、StorybookのVRTを自前実装や、VRTを行うStoryを絞る工夫が必要になるかもしれません。
MSW
APIモックにはMSWを利用しています。コンポーネントはContainer/Presenterで実装しているため、Storybookでの利用はありませんが、VitestによるAPI呼び出しテストやレスポンス検証で重宝しています。MSWのhandlerは後述のOrvalで自動生成しています。
スキーマ
TypeSpec
弊チームでは、スキーマを最初に定義し、合意を得てからAPIの実装に入ることになっています。
APIのスキーマはTypeSpecというMicrosoft製DSLを採用しています。TypeScriptライクな記法でAPI仕様を記述し、OpenAPIやJSON Schemaなど複数フォーマットへの出力が可能です。
OpenAPI YAMLを直接記述すると非常に冗長で記述コストが高まりますが、TypeSpecはTypeScriptに近い記法で標準のフォーマッタやコンパイル時のエラーチェック機能も備えています。また、コンパイルでは検出できないエラーに対応するため、出力したYAMLについてはRedoclyで追加チェックを行っています。まだメジャーバージョンリリース前ですが、これまで大きな問題は発生しておらず、非常に気に入っています。
その他ライブラリ
Orval
TypeSpecで生成したOpenAPI YAMLからTypeScriptコードを生成するためにOrvalを採用しています。
OrvalはOpenAPIを入力としてZodスキーマ、APIクライアント、MSWハンドラを自動生成でき、ドキュメントが充実し、多様な出力フォーマットにも対応している点が魅力です。
date-fns
日付操作ライブラリとしてdate-fnsを採用しています。比較対象としてDay.jsがありますが、社内採用実績が豊富で、私自身も馴染みがあるdate-fnsを選びました。副作用がなく、純粋関数である点が良いです。
Zod
TypeScriptとの相性が良いスキーマ検証ライブラリとしてZodを採用しています。Valibotも候補でしたが、Conformの公式サポートや社内の経験値を考慮し、Zodを選びました。
プロダクト内のドメインや共通型はまずZodスキーマで定義し、型定義とバリデーションを一元化しています。APIレスポンスからフロントエンドアプリケーション向けの型変換も、Zodでparse
して実行時の型検証の代わりにしています。
Conform
Conformを採用しています。私が知る限り、Server Actionsに対応している唯一のライブラリであり、App Routerで利用する場合、現状最有力の選択肢と考えています。Zodでのバリデーションにも対応している点が便利です。
next-intl
多言語対応が必要なため、App Routerに対応したnext-intlを採用しています。サーバーコンポーネント、クライアントコンポーネントのいずれからも統一的なインターフェースで言語取得が可能で、サーバーとクライアント境界を跨ぐ共通コンポーネント実装時に助かっています。
nuqs
クエリパラメータの同期を容易にするため、nuqsを導入しました。
nuqsはServer/Client双方に対応し、クエリパラメータを型安全に取得・操作できます。Server ComponentではsearchParamsのバケツリレーが必要でしたが、nuqsとreact.cache
により、この手間を減らせる点がありがたいです。
まとめ
ここまでご紹介したように、フロントエンドプロジェクトの立ち上げ時には、フレームワーク、ビルドツール、ライブラリ、テスト環境など、あらゆる側面で試行錯誤が求められます。私たちのチームでは、必要に応じてNext.js App RouterやTypeSpec、Conformなどの新しい技術を積極的に採用し、日々「どうすればより良い開発体験を実現できるか」を考え続けています。
もちろん、すべてがスムーズに進むわけではなく、新しいツールや手法を導入するたびに課題が見つかり、試行錯誤を繰り返しています。新たな選択肢も含め、これからもより良い開発環境を目指して模索していくつもりです。
もし、「その試行錯誤、面白そうだな」「一緒に悩みつつ、改善を続けていくプロセスに参加してみたい」と感じた方がいれば、ぜひ私たちのチームに加わっていただきたいです。最新技術を駆使しながら、環境問題解決へ向けたプロダクトづくりを、一緒に前進させていきましょう!
Discussion