【入社エントリ】機能開発を止めずにWebフロントエンドを改善
はじめまして、2024年8月にPIVOTにジョインしました、@tawachanです。ジョインしてからそろそろ半年弱経とうとしていますが、簡単な自己紹介とその間に主に取り組んだ、PIVOTでのWebのフロントエンドの改善について書いていこうと思います。
自己紹介
基本的に小さな会社やチームでソフトウェア開発をしており、フロントエンドを中心にしながらもバックエンドやインフラ側にも携わってきました(最初の最初は Oracle Database で PL/SQL を書いたりしていたので、ぜんぜん違うことをやるようになりました)。
途中でフリーランスとして仕事をしたり、合間に大学院(政治学)に行ったり、紆余曲折あって、PIVOT にたどり着きました。
「PIVOT」はビジネス映像メディアと銘打っていますが、ビジネス領域に留まらず、政治や社会など、経済やお金に必ずしも直結はしないけれども重要なテーマを幅広く取り扱う珍しいメディアだと思い、その成長の一端に携わりたいと思い、入社いたしました。
本記事のテーマ
自己紹介はそこそこに、本記事では主に私が入社して半年弱の間に行った取り組みについて紹介します。
PIVOT では、私が初めてフロントエンドを中心にやってきた人だったので、フロントエンド周りのタスクは基本的に自分が担うことになりました。蓋を開けて見てみると、(歴史的背景により)React でありながら、適切なご作法にあまり則り切れておらず負債が少々ある状況でした。
過去の自分の経験から考えても実装効率がだいぶ低く、機能開発、更には事業成長の足枷にもなると思い、ここを入社直後の「最初に取り組むべき課題」としました。
本記事では、この課題への取り組みを「フロントエンドの開発効率を改善した事例」として紹介できればと思います。
中長期的には開発効率を上げるための改善が重要なのはわかりつつも、当然実装すべき機能も数多ある中で改善を進めていくのは、必ずしも容易ではありません。それでもなんとか機能開発を止めない範囲で、できる限りの改善を入れていく一事例として、少しでも参考になれば幸いです。
入社当初のフロントエンドの課題
PIVOT の Web フロントエンドは Next.js で実装されていて、スタイリングには SCSS が使われています。これらの技術選定そのものには大きな問題はありませんが、次のような課題がありました。
コンポーネントの責務が不明確
Template や Component など、何かしらの意味でディレクトリが分かれ、多層的にコンポーネントが分割されているのですが、それぞれの責務が不明確な状態でした。それにより、再利用可能性が低く、認知コストは高いという問題を引き起こしていました。
- 一見再利用できそうな下層のシンプルな見た目のコンポーネント内で、実は API を呼び出しており、その見た目だけを使いたい箇所で使えない
- Props ではなくコンポーネント内部で不必要に Context を参照しており、外部から挙動が分かりづらい
どこで何が行われているのかを把握するだけでも認知コストが高く、修正時に影響範囲を特定しづらい状態でした。また、状況を把握したとしても、あまり共通化ができず、つらみが増していました。
SCSS 運用の複雑さ
これは好みの問題でもありますが、SCSS ですと JSX とは別のファイルにスタイルを書くことになるので、ファイルを行き来するのが手間に感じます。また TypeScript の型の恩恵も受けられないので、実装に注意力が必要です。
さらに、BEM を適用したようなクラス名になっていたので、階層構造を変えるとスタイリング全体を修正する必要が出るなど、コンポーネント指向の開発には不向きなところもあり、複雑度を高めていました。うまく運用すれば問題ないのかもしれませんが、命名規則も煩雑で保守性が低下していました。
不必要な自前実装
ボタンやモーダル、ドロワーなどの基本的な UI コンポーネントも独自実装しており、アクセシビリティやユーザビリティの標準的な期待値を満たせていない状況でした。
実績のある UI ライブラリを活用することで、これらの基礎的な品質を効率的に担保できたはずですが、基本的なコンポーネントまで独自実装していたことで、開発リソースが分散し、本来注力すべき機能開発に時間を割きづらい状況でもありました。
その他の細かいところ
useMemo で済む計算が useEffect と setState で処理されているなど、React のアンチパターンがあったり、無駄に複雑で効率的ではない実装が散見されました。挙動を正確に把握するにはコードを実行する必要があり、リファクタリングにも大きなコストがかかる状況でした。
今期実施した改善施策
基本方針:機能開発を止めない
上記の課題を解決するためには、実装自体を大幅に変える必要があります。しかし、全体をまとめてリファクタリング等をすると機能開発に支障が出るため、現実的ではありませんでした。
そこで、可能な限り抜本的にコードを改善しつつも、並行して機能開発を進めるために、古いコードベースをまとめてサブディレクトリ(今回は unorganized-components)に移管し、修正すべきファイル群を明確にしました。
その上で、機能開発で新しく実装する箇所や既存の改修箇所を、可能な限り新しい方針(後述)に則って実装していく、という方針で進めました。ボタンや動画のサムネ表示など、すでに存在するコンポーネントも該当箇所は基本的にすべて実装し直しました。
その基本方針の上で、具体的にどのような変更を入れたのか紹介します。
ディレクトリ構成の整理
コンポーネントの責務が不明確だった課題に対処すべく、ディレクトリ構成を整理しました。
まずは、最近流行りの Package by Feature を導入し、その中で Container/Presentational パターンを採用しました。具体的には以下のようなディレクトリ(例)を追加しました。
features/
└── account
├── feature-a.container.tsx
├── feature-a.presenter.tsx
├── feature-a.presenter.stories.tsx
├── feature-b.container.tsx
├── feature-b.presenter.tsx
├── feature-b.presenter.stories.tsx
├── account-common.tsx
└── account.hooks.ts
機能(Feature)ごとにディレクトリを分け、その中で大きく hoge.container.tsx と hoge.presenter.tsx を置くようにしました。さらに、機能間で共通で使いそうなコンポーネントやロジックも、必要であればフラットに同階層へ置くことを想定しています。機能依存のコードは他で再利用されづらく、Package by Layer のように整理する旨味も少ないので、このようにまとめています。
一方で、機能横断で共通で使う見た目やロジックは当然あるため、そこは従来どおり Package by Layer 的に残し、適宜棲み分けています。全体としては以下のような構成(抜粋)です。
├── features
├── components
├── hooks
├── pages
├── services
└── util
各ディレクトリの棲み分け(現時点の方針)は以下のとおりです。
-
components: 機能に依存しない共通の見た目を定義する。主に Radix UI を使った基礎的なコンポーネントを配置。 -
hooks: 特定の機能に依存しない hooks を配置。機能に依存する hooks はfeatures配下。localStorageやIntersectionObserver関連など。 -
pages: Next.js の仕様。主にfeatures内の Container を呼び出し、Page 単位の値(Path/Query)取得やルーティングを担う。 -
services: フロントエンドで持つドメインロジックを集約。 -
util: ドメインにも関係ない便利関数を配置(Assertion、Time など)。
上記いずれかのディレクトリに入っていれば、改善済みのコードベースとして参照できる想定にし、実装のたびに可能な限り unorganized-components を使わないよう置き換えていきました。
Tailwind CSS、Radix UI(+ shadcn/ui)の導入
SCSS で運用・開発していることのつらみは前述のとおりで、ここを刷新したいと考えていました。
以前は Chakra UI を使っており開発効率の高さは実感していましたが、PIVOT は C 向けのプロダクトでデザインにこだわりがあるため、既存でスタイルが当たっているライブラリだと、カスタマイズにむしろ手間がかかる懸念がありました。
そこで Headless UI である Radix UI を使い、相性のよい Tailwind CSS をスタイリングとして採用しました。また、shadcn/ui という Radix UI と Tailwind CSS の組み合わせで事前に用意されている UI コンポーネントの存在も選定の大きな要素でした。これを活用することで、一定程度「当たり前品質」を担保しやすい仕組みになると考え採用しました。
Chakra UI に比べれば、自前で基礎的なコンポーネントを管理・修正する手間が多少ありますが、shadcn/ui の力を借り、そこからさらにデザインシステムに沿うコンポーネントを作っていくと、結果的に大きな差はなかったようにも思います。
最初は若干時間がかかるものの、必要なコンポーネントや仕組みが揃ってくると、徐々に開発速度が上がる実感がありました(定量計測の仕組みは現状ないので、そこはチームとして要改善)。Tailwind CSS はユーティリティ・ファーストと言われるように、最初に学習コストはあるものの、慣れれば用意されたクラスを組み合わせるだけでスタイリングでき、SCSS より効率・保守性を高められると感じました。
実際に改善施策を進めてみて
入社後の大きな機能として「ホーム画面の刷新」と「ソーシャルログイン」を実装しました。これらを含め、フロントエンド開発はほとんど自分ひとりで担当しましたが、スケジュール的にも当初想定どおりにリリースできました。
特にホーム画面刷新は既存の見た目に大きく関わるため、コードベースも大きく書き換えていく絶好の機能開発でした。これに着手する前に今回のフロントエンド改善施策を定義できたのは、良い意思決定だったと思います。
「機能開発と並行して進める」ことを至上命題として進めましたが、技術的な改善と事業成長は必ずしもトレードオフではないことを改めて実感しました。最初はボタンなど基本コンポーネントを拵える必要があり、速度は出ず少々遅れ気味という印象の時もありました。
しかし後半は再利用できるものが増え開発効率が上がりました。体感としても、従来の方法で書き続けるより、実装すべきものが明確になり心理的にも気持ちよく開発できる環境となり、スピードも出せるようになってきました。バランスよく着手すれば、改善コスト以上に開発効率が改善され、むしろ機能開発も同等かそれ以上に速くなる余地すらあると感じています(PIVOT の場合は伸びしろが大きかっただけかもしれませんが)。
今後の展望
この半期で少しずつフロントエンドの改善を進めてきましたが、当たり前水準を達成するにはまだまだ課題が山積です。今後も事業成長と技術的改善のバランスを意識しながら、開発者体験を維持・改善しつつ、ユーザーにとって価値のあるプロダクト提供に努めていきます。裁量を持って自分たちが開発しやすい環境を形作っていけるのは、とても良い経験でした。
PIVOT株式会社のプロダクト開発チームが運営するテックブログです。プロダクト開発における技術的知見やチーム運営の工夫を発信していきます。 採用情報はこちら → pivot.inc/recruit/
Discussion