はじめに
はじめまして、スナックミーでフロントエンドエンジニアをやっている前田です。
入社3年目にして初のブログ投稿なので、緊張しています。
さて、今回は新規サービス立ち上げに際し0⇒1で技術選定に取り組む機会があったため、その経緯をブログとして記録に残したいと思います。
弊社では定期便の会員様向けに、お届け内容を設定できるマイページを提供しています。またオンラインストアでは、定期便でお届けしているおやつの大袋や、期間限定の特別なおやつ、ドリンクなど、おいしい商品を幅広く販売中です。
その中でも特に根強い人気を誇る商品『CLR BAR』と『good JerQy』を定期便としてお届けするサービスを、今回新しく開発することになりました。
UI ライブラリ・フレームワークについて
昨今では React、Vue.js、Svelte、SolidJS、Astro など、数多くの UI ライブラリやフレームワークが台頭してきていますが、その中でも snaq.me のマイページでは React を使用しています。ほかのライブラリに依存した要件がなく、特に不満もないので React を選びました。
また、ほぼパーソナライズ情報しか表示しない会員向けページが主コンテンツであり SSR や SSG のメリットをあまり受けられないと判断し、SPA で構築しています。
今となっては Next.js (SSG) は利用しても良かったかもしれないと考えていますが、本プロジェクトの着手時には App Router がまだ stable になっておらず、 Static export も未実装だったため React + React Router を使用しました。
アーキテクチャについて
bulletproof-react を参考にしました。弊社ではライブラリ周りに違いがある(CRA を使っていないなど)ためやや改造していますが、概ね同じ構成となっています。
features によるドメインの分類を行わず、src 直下の各ディレクトリに追加していくほうがシンプルな設計ではあるので、リリース初期は作業ペースが安定するのでは?という先入観を持っていましたが、サービスのドメインを分析して整理していくやりかたは、むしろ最初からやっておくべき工夫だと考えています。
スタイルについて
snaq.me のマイページでは styled-components を使用していますが、 使用するタグに頓着しづらい傾向がある点などに以前から課題を感じていたため、別のライブラリも使用することに。
styled-components を使用する上での課題は、命名規則や Tagged Templates の使用禁止などによって回避可能ではありますが、vanilla-extract など className でスタイルを適用するライブラリを使えば自然と解決できそうです。
しかし、今回はこのような制約があります。
- リリースまでの期間がややタイトである
- リリース初期は CSS を書く時間がどうしても増えがちだが、 Object Style だと
'
や"
の分、ややタイピング量が増えてしまう - CSS の細かな調整が必要な UI があまりない
結果として「スピード優先!細かなスタイルにはこだわらないでいい」ということで、UI コンポーネントライブラリである Chakra UI を使用することになりました。どの UI コンポーネントライブラリを使用するかという議論については、チームメンバーの経験から「キャッチアップの工数が低いもの」という観点で選んでいます。
また、Chakra UI を使用した場合は以下のような課題もあるため、今後の成長に合わせて設計変更や移行を検討したいと考えています。
- やや型が弱い
mb="15"
など存在しない値を指定しても静的なエラーが出ない- テーマのカスタマイズが補完されない(colors にブランドカラーを追加したときなど)
- バンドルサイズが大きい
- https://bundlephobia.com/package/@chakra-ui/react@2.7.0 : 202KB !!!!!
- JSX がスタイルの指定で膨らむ
- 適宜コンポーネントを分けていれば私は気にならないのですが、気になる方もいるようなのでメンバー構成の変動に合わせて検討
状態管理
snaq.me のマイページでは Redux を使用しています。大規模なサービスでは多くのグローバルステートが必要となり、それらを管理する目的としてなら Redux はいい選択肢です。
しかし、 新サービスは機能要件的にあまり多くのグローバルステートを必要としないため、より小さい構成で始められる Recoil と Jotai のどちらを使うかという検討を行いました。
ファーストリリース時の機能要件的にはどちらでも(もしくはどちらも使わず Context AP でも)満たすことが可能ですが、結論としては、下記の理由により Jotai を選択しました。
- サービスが成長していくにつれ、Context API は Provider のネストが深くなりがち
- Recoil も Jotai も導入はさほど難しくないので、 Context API でなく最初からどちらかを入れる
- key の指定が不要で、バンドルサイズも小さいため Jotai を選択
機能要件的にどちらも満たせるとはいえ、 Recoil にも多くの integration が存在し、人気のあるライブラリです。
今後、サービスが成長していくにつれ移行が必要になる可能性は十分にあると考えています。そのため、Jotai を各コンポーネントなどで直接参照せず、カスタム Hook などを経由して値の取得・設定をするようにしています。
テスト
主に Vitest と React Testing Library を使用しています。
API 疎通が必要な箇所については、MSW で API レスポンスをモックしています。
また、E2E テストとして Playwright を導入し、いくつかの画面で動かせる状態にはしていますが、CI などの運用フローに含めるところまで成熟させられていないのが現状です。
E2E テストを安定させるためには API のモッキングが必要となりますが、通常の UI 実装のための vite のサーバープロセスと、 MSW で API がモックされたサーバプロセスの二つを用意する必要があり、開発端末のスペック的に両方を起動し続けることが難しかったことが要因です。
Playwright でも Component Test が試験的に導入されつつありますが、その用途であれば React Testing Library によるユニットテストのほうが高速に動作するため、今は Vitest と React Testing Library に主軸を置いています。
その他細かなライブラリ
- Vite
- snaq.me のマイページでは webpack を使用していますが、コードベースが巨大になった影響でファイル保存時の CPU やメモリ負荷が高いため、ノーバンドルツールを選択しました。
- axios
- interceptors でリクエストに認証情報を付与するほか、500レスポンスを検知して通知するために使用しています。axios 自体があまり型が強くない点と、axios をラップした関数を作成しているため、fetch に戻すことも検討中です。
- neverthrow
- 主にAPI エラーをハンドリングするために使用しています。
- React Router v6
- ページパスの型補完は Mobile Factory さんの記事を参考にして実装しました。
- dtsgenerator
- API が OpenAPI を出力してくれるので、それを型定義に変換するために使用しています。
- OpenAPI を axios や fetch のラッパーなどに変換してくれるツールも存在しますが、弊社では OpenAPI から型情報だけを得たかったので dtsgenerator を選択しました。
おわりに
というわけで、新しくリリースされたサービスはこちらです。
私の推しは『good JerQy』の黒糖醤油味ですが、他フレーバーも『CLR BAR』もおいしいので、ぜひお試しください!
またスナックミーはエンジニアも積極的に募集していますので、興味のある方はぜひ応募していただけると嬉しいです!
募集ポジション
ソフトウェアエンジニア Backend (lead) / 株式会社スナックミー
ソフトウェアエンジニア FrontEnd (lead) / 株式会社スナックミー
ソフトウェアエンジニア Data Engineer / 株式会社スナックミー
CLR BAR
https://portal.snaq.me/checkout/flavor-select?brand=clrbar