🐣

React SPA の技術選定で考えたこと(atama plus のケーススタディ)

2022/03/17に公開1

atama plus の osuzu です。

atama plus では、これから段階的に Web ベースプロダクトのフロントエンド開発で React を用いて SPA(Single Page Application) へリプレイスしていきます。

参考: 技術課題のないプロダクトなんてものはない!Django→React リプレイスの意思決定に至る atama plus 流の軌跡

この記事では SPA の技術選定にあたって考えたことを共有します。

プロダクトについて

技術選定はプロダクトの置かれた状況によって意思決定が変わると考えているので、リプレイスするプロダクトについて補足します。

atama plus は塾などで利用可能な学習アプリ「atama+」を提供していますが、一連のプロダクトの中に塾本部の方が管理のために用いる業務アプリがあります。

今回リプレイスするのはこちらの業務アプリです。

レンダリング方針

業務アプリのレンダリング

結論として、私達は React による CSR(Client Side Rendering) で SPA を構築していくことを選択しました。

最近の Web アプリは SG(Static Generation) 方式を選択すべきケースが多いと考えていますが、今回のアプリは SEO 要件のないログイン専用の業務アプリであったため適さないと判断しました。

残った選択肢の中で、 SSR(Server Side Rendering) と CSR は下記の要件を元に CSR を選択しました。

  • Static Generation できないとはいえ、可能な限りフロントエンドは Static に配信したい
  • 業務アプリという性質上、FCP(First Contentful Paint)の重要度を低く見積った
  • CSR では初期ロードが重くなる代わりにキャッシュ面のメリットがありうる
  • 既存のプロダクトを段階的にリプレイスしていくため、React Framework (Next.js や Remix.run)へ可能な限り依存したくなかった

React エコシステムのライブラリ選定

技術選定にあたっては特に下記を重視しました。

  • DX(開発者体験)向上にフォーカス
  • React18 アップデートを視野に

Build Tools

【候補】

  • Create React App (Webpack)
  • Vite

【選定】

Vite

【選定理由】

Vite の採用によって圧倒的な開発体験の向上となるためです。加えて開発環境以外では Rollup でビルドされる安心感もありました。

React SPA を始めるには Create React App が簡単ですが、ツールチェインを組み合わせられる場合はぜひ Vite (esbuild) 採用の検討をお勧めします。

The State of JS 2021 においてもトップの満足度だったように、ゲームチェンジレベルの開発体験の向上が起こりえます。

Routing

【候補】

  • React Router
  • React Location

【選定】

React Router

【選定理由】

React Location は、React Router と比べ CSR においてパフォーマンスに優れた SPA を作るための機能を持っていますが、技術選定をした時点では React Location に関する下記の懸念を解消できず、React Router を採用しました。

  • SWR などの hook を通した際に、Data Fetch のロジックが散らばりうる
  • Suspense や ErrorBoundary といった React コンポーネントとの棲み分けを考える必要がある
  • prefetch 機能を有効に活用するための敷居が高い

今回は採用を見送りましたが、React Location 自体はとても強力なツールになりえるものだと考えているので、こういう設計・状況で用いたなどの知見を共有いただけると助かります 🙏

Global State Mangament

【候補】

  • Redux Toolkit
  • (SWR or React Query) + Context API
  • (SWR or React Query) + (Recoil or Jotai or Zustand)

【選定】

SWR + Context API

【選定理由】

私達がリプレイスするプロダクトは、管理する状態の多くが data を Read してレンダリングするものとなり、操作によって書き換わる状態は少なかったため、Redux ではなく SWR を採用しています。

現在はリプレイス初期ということもあり Global State 自体が多くないため、data fetch/cache 以外の状態は Context API で管理していますが、今後再レンダリングの最適化が難しくなるなどパフォーマンス上の問題が発生した場合、Recoil や Jotai や Zustand などの小さく取り入れることも出来る State Management を採用する可能性があります。

UI Library

【候補】

  • Chakra UI
  • MUI
  • Tailwind CSS

【選定】

Chakra UI

【選定理由】

Chakra UI と Tailwind CSS を比較した際に、型安全などエンジニアにとっての開発体験から Chakra UI を採用しています。

加えて今回は基となるデザインやそのパターンが存在したため、既存の UI ライブラリをそのまま利用は出来ないという前提でした。Chakra UI と MUI を比較しカスタマイズ製やカタログの必要十分さなどから Chakra UI を選定しています。

Testing

【候補】

  • Jest (or Vitest)
  • Testing Library
  • Mock をどこでやるか (MSW or Jest mock)
  • Storybook を使うか

【選定】

  • Jest
  • Testing Library
  • MSW(Mock Service Worker)
  • Storybook

選定理由

Jest と Testing Library に関しては現時点で特に比較対象なく採用しています(デファクトを採用しています)。


現在フロントエンドのテスト方針として、単体テストを網羅するのではなく、出来るだけ Component Tree の一番上(Router Provider の直下)で、実挙動に近くなるようユーザーストーリーとしてテストを書きます。

そのため挙動の正確さを求め MSW を採用して、 Request の Response をモックすることにしました。


Storybook に関しては、メンテの大変さが想定されますが、atama plus は Uniform というデザインシステムを運用しており、UI パターンのカタログにも利用可能なため Storybook の運用がペイすると現時点では判断しています。すべての UI を担保するわけではなくシステム全体に用いられるような重要なパターンのみ記述します。

また Storybook は Vite 上で動作するのでこの組み合わせもお勧めです(正確には Storybook の一部のみ Vite で動作し、Webpack の依存は消えない)


※ Vite Native で動作する Vitest の開発が進んでおり(※執筆時点)、Production Ready になった段階で開発体験向上のために Jest を Vitest へ切り替えたいと考えています。

その他お世話になってるライブラリ

  • Aspida
  • react-hook-form
  • react-use
  • react-icons
  • react-error-boundary

この中で特に Aspida に助けられています。REST API がバックエンドの環境でも、クライアントの開発体験として快適なレベルの型安全が手に入るためです。

※既存のバックエンドとの差分の少なさや社内の経験値の理由から、私達は GraphQL ではなく REST API を選択しています。

目指したかったこと

Web フロントエンドは毎年のように新しいビルドツールやライブラリが登場していますが、実際にその多くが開発を便利にそして簡潔にしてくれます。いつも本当に助けられています。

組織やチームにとってツールの良い変化は取り入れながら、開発者にとって開発することが少しでも楽で楽しくなるような環境になることを目指しています。それが結果的に本質的な価値へ向き合う近道だと信じて。

Discussion

Teruhisa - T6ADEVTeruhisa - T6ADEV

今後再レンダリングの最適化が難しくなるなどパフォーマンス上の問題が発生した場合、Recoil や Jotai や Zustand などの小さく取り入れることも出来る State Management を採用する可能性があります

SWRで進められていると理解しつつ。。。
Jotaiですと、React Queryとの連携がサポートされていたりします。ご参考までに🙏
https://jotai.org/docs/integrations/query