TVerのWebフロントエンドエンジニアのJeun Yun (Paul) Tsangです。2024年から始まったWebフロントエンド開発の内製化によって改善した、 tver.jp のUI構築の考え方を紹介します。
tver.jpの技術スタック
以下は改善後のフロントエンド技術スタックです。
- Next.js(クライアントサイドレンダリングのみ)
- CSS ModulesとSassでスタイルの定義
- StorybookとChromaticでコンポーネントの検証
- Radix PrimitivesのUIライブラリーで汎用コンポーネントの構築
- Biomeのコードフォーマッターとリンター
内製化で見えてきた課題の全体像
私は2024年10月にTVerへ入社してから、新しい機能を実装しながら既存コードの可読性や使いやすさの問題に気づきました。これまで外部パートナーに依存していた開発体制では、コードの統一性やアーキテクチャの一貫性に課題もありました。フロントエンドチームの内製化を進める中で、長期的なビジョンを描きながら品質向上と開発効率を改善するために、チームで課題を洗い出した結果、主に以下の3つの領域で問題があることが明らかになりました。
1. アーキテクチャの混乱による開発効率の低下 - コンポーネントの責任分界が不明確で、可読性・メンテナンス性に課題 - 再利用性を考慮した設計になっておらず、カスタマイズ性・拡張性に問題
2. デザインシステムの未整備による非効率な開発 - 統一されたデザイン仕様がなく、個別実装による重複コードの発生 - CSS管理が非効率で、開発者の使いやすさ・生産性に課題 - 類似コンポーネントでもUIの見た目や挙動が微妙に異なり、ユーザー体験の一貫性が損なわれる問題
3. 開発・テスト環境の不備による品質担保の困難 - コンポーネント検証環境の不足 - UIの変更影響範囲の把握が困難
これらの課題を解決するため、以下の改善戦略で取り組みました: - アーキテクチャの再構築によるコードの整理と責任分離 - デザインシステムの導入と標準化による開発の標準化と効率化 - 開発・テスト環境の整備による品質向上
課題解決のアプローチと実装した施策
1. アーキテクチャの再構築
新しいコンポーネントの実装やデザインシステムを導入する前に、根本的なアーキテクチャを改善する必要がありました。既存の構造的な問題を解決せずに新機能を追加しても、可読性とメンテナンス性の課題が残り続けてしまうためです。そこで、まず以下のアーキテクチャ改善に取り組みました。
Feature-Sliced Designによる可読性とメンテナンス性の改善
特に気になったのは、コードの可読性とメンテナンス性に関する構造的な問題でした。すべてのコンポーネントが単一の/components
ディレクトリに配置されており、汎用的なUIコンポーネントと特定の機能やページに紐づくビジネスロジックを含むコンポーネントが同一のフォルダーに混在していました。例えば、Button
のような再利用可能なUIコンポーネントと、EpisodePageContainer
のような特定のドメインに関連するコンポーネントが同じフォルダに並んでいる状況でした。
この問題を解決するために、Feature-Sliced Designのアーキテクチャを採用しました。Feature-Sliced Designは、shared
(共通関数、汎用コンポーネント)、features
(機能)、pages
(ページ)などの層に分けることで、コンポーネントの責任と依存関係を明確にするアーキテクチャパターンです。これにより、どこに何があるかが直感的に理解でき、コードの可読性を改善できました。
さらに、Container/Presentationalコンポーネントのパターンが採用されていないため、データ取得やビジネスロジックとUI表示の責任が混在したコンポーネントが多数存在していました。これにより、コンポーネントのファイルサイズが肥大化し、理解と修正が困難になっていました。
この問題に対して、Container/Presentationalコンポーネントのパターンを導入してデータ取得とUI表示の責任を分離し、コードの可読性とメンテナンス性を改善しました。Feature-Sliced Designと合わせて、ビジネスロジックも独立した関数に分割することで、単体テストの作成が容易になり、再利用性も向上しました。
以下の例では、ContainerでデータとビジネスロジックをUIコンポーネントから分離することで、可読性とテストしやすさを改善できました。
// EpisodeContainer.tsx const EpisodeContainer = () => { const episodeData = useEpisodeData() // ロジックの分離で再利用性の向上と単体テストの実装がやりやすくなる const episodeStatus = getEpisodeStatus(episodeData) return <Episode episode={episodeData} status={episodeStatus} /> } // Episode.tsx const Episode = (props) => { return ( <section> <h1>{props.episode.title}</h1> </section> ) }
コンポーネント設計による拡張性の向上
既存の汎用コンポーネントは、CSSとPropsを簡単にカスタマイズできない状態で、コンポーネントの再利用性に問題がありました。新しいデザイン要件が発生した際に、既存のコンポーネントを拡張するのではなく、似たような新しいコンポーネントを作成してしまうことが多く、コードの重複や管理の複雑化を招いていました。
上記の可読性とメンテナンス性の改善により、ビジネスロジックを分割した後、コンポーネントの機能を達成するために必要なPropsとCSSを簡単に設定できるようになり、コンポーネントの再利用性を改善できました。今後、新しいデザイン要件により既存コンポーネントの新しいバリエーションが必要な場合は、元のコンポーネントを修正したり完全に新しいコンポーネントを作成するのではなく、既存コンポーネントと分割した関数の機能を活用しながら拡張したコンポーネントを簡単に実装できます。これにより、コンポーネントの再利用性と開発の生産性を向上できました。
2. デザインシステムの導入と標準化
アーキテクチャの改善により安定した基盤が整った後、次にデザインシステムを基盤とした新しいコンポーネントの実装に着手しました。以下、デザインシステム導入の取り組みについて紹介します。
Radix Primitivesによる高品質なコンポーネント基盤の構築
TVerは自社のデザインで開発しているため、これまで外部のUIフレームワークを採用していませんでした。汎用コンポーネントの再実装とデザインシステムの導入をきっかけに、自社UIフレームワークを作ることが決まりました。しかし、ゼロから全ての機能を実装するのではなく、業界標準の仕様は外部パッケージに任せ、TVerの独自デザインの実装に集中できる開発環境を選択しました。そこで、汎用コンポーネントの再実装を始める前に、ヘッドレスUIライブラリであるRadix Primitivesを採用することにしました。
Radix PrimitivesはヘッドレスUIライブラリで、CSSが含まれていないため、簡単に独自のCSSを設定でき、高いカスタマイズ性を持つツールです。
採用理由
- アクセシビリティ対応が充実しており、ドキュメントにも詳細に記載されている。将来TVerのアクセシビリティを改善したいと考えているため、不可欠な条件であった。
- コンポーネントを個別にインストールできるため、不要なコードを削減できる。
- TVerで必要なコンポーネントが提供されており、ドキュメントもわかりやすい。
デザインシステムを元に汎用コンポーネントの再構築
チームの内製化以前は、汎用コンポーネントがありながらデザインシステムの意識が不足しており、各画面のデザインを統一することが困難な状態でした。デザインシステムに基づいたコンポーネントが存在せず、毎回個別にCSSを定義する必要があったため、コンポーネントの使いやすさに問題がありました。また、デザイン仕様の統一も困難で、全体的に開発生産性に課題がある状態でした。
特に問題だったのは、同じ機能を持つボタンでも、ページによって微妙にスタイルが異なったり、フォントサイズや余白の値がバラバラだったりと、UIの一貫性が保たれていないことでした。これにより、ユーザーが同じサービス内で異なる体験をしてしまい、ブランドの統一感やユーザビリティが損なわれていました。
そこで、デザインシステムの導入とコンポーネント基盤の整備により、開発の標準化と効率化を進めました。
デザイントークンとSassによる開発効率の向上
開発者の使いやすさと生産性の観点では、CSSの管理と開発体験に課題がありました。CSSの変数はカラー定義のためにしか使用されておらず、Sassも活用されていない状態で、重複したCSSが多く存在していました。これにより、開発生産性や仕様統一の難しさが問題となっていました。
この課題を解決するために、デザイントークンベースのCSS変数システムと、Sassのmixin機能を活用したスタイル管理を導入しました。レスポンシブデザインやタイポグラフィ、スペーシングなどを統一された方法で管理できるようになり、開発効率と品質の向上を実現しました。
例えば、以下のように毎回media queryを記述する代わりに、Mixinとして定義することで仕様の統一とメンテナンス性を改善できます。
@mixin sp { @media screen and (max-width: 845px) { @content; } } .component { @include sp { // スマホサイズのCSS } }
デザイントークンによるスタイル統一の実現
当時、デザインチームでもFigmaにデザインシステムの導入を開始していたため、それをきっかけにデザインシステムの概念を意識しながら新しい汎用コンポーネントを作り直し始めました。デザイントークンの整備も並行して行ったことで、CSSの実装がより効率的になりました。
従来の実装方法
カラーに関する変数は存在していましたが、各画面のコンポーネントに個別のCSSを設定していたため、メンテナンス性とコンポーネントの再利用性が低下していました。また、ホバーやフォーカスなどのインタラクティブなスタイルの実装も困難な状況でした。
例えば以下のように、背景色は同じでありながら個別に設定されており、同じ仕様であるはずなのにPaddingが異なるといった問題がありました。
.button-a { padding: 12px; background: var(--bg-blue-btn); font-size: 14px; } /* 同じ仕様はずのにpaddingが違う */ .button-b { padding: 10px; background: var(--bg-blue-btn); font-size: 14px; }
改善後の実装方法
カラーの変数だけではなく、PaddingやTypographyのデザイントークンを作成し、コンポーネント全体を共通化して必要なバリエーションを定義することで、コンポーネントの再利用性と使いやすさを改善しました。Figmaに同じバリエーションが定義されているため、デザインと同じ変数名を設定すれば、簡単にデザイン通りのUIを再現できるようになりました。
以下の例では、各Buttonのバリエーションを汎用コンポーネントに定義し、使用時にはバリエーションのPropsを設定するだけで、必要なデザイン要件を迅速かつ簡単に実現できるようになりました。これにより、アプリ全体のUI統一も改善できました。
/* Button.module.scss */ .primary { background: var(--Surface_Accent); color: var(--Content_Inverse); &:disabled { background: var(--Surface_Disabled); color: var(--Content_Disabled); } } .sm { @include Button_S; // TypographyのMixin padding: var(--Spacing_0) var(--Spacing_150); // Paddingの変数化 height: 24px; }
// Buttonコンポーネントを置くだけで楽にUIを実装できる <Button color="primary" size="sm" />
3. 開発・テスト環境の整備
アーキテクチャの再構築とデザインシステムの導入により、新しいコンポーネントが作成できるようになりました。しかし、これらのコンポーネントの品質を継続的に担保するためには、適切な検証とテスト環境が不可欠でした。そこで、開発・テスト環境の整備に取り組みました。
StorybookとChromaticによる品質向上
内製化以前は、Storybookを採用せずにコンポーネントを実装していました。そのため、コンポーネント単体での状態検証やテストが困難で、開発の生産性と品質に課題がありました。
この課題を解決するため、コンポーネントの可視化やテスト環境を改善するためにStorybookとChromaticを導入しました。Storybookを使用することで、データや実際の画面がなくてもコンポーネントの状態を検証でき、プロトタイピングが容易になります。Chromaticは無料プランでもStorybookのホスティングとVisual Regression Testing (VRT) が利用できるため、導入しやすいツールです。UIのビジュアルテストにより、CSSとHTML構成の変更がユーザーに見えるUIへ与える影響範囲を検証でき、品質の担保が効率的に行えます。さらなる品質向上のため、今後はインタラクションテストやビジュアルテストの強化を計画しています。
ChromaticのVRTとUIレビュー機能により、CSSとHTML構成の修正によって発生するUI変更を効率的に検証できます。
今後の取り組みと最後に
今回は、TVer Webフロントエンドの内製化によって改善したtver.jpのUI構築について紹介しました。内製化の1年目で、すでに多くの課題を解決し、開発体験を向上させることができました。しかし、まだ解決すべき課題は多く残っており、今後もユーザー体験の向上はもちろん、アプリのパフォーマンスや開発生産性のさらなる改善に取り組んでいきたいと考えています。
例えば以下のような点です。
- サーバーサイドレンダリングの導入
- StorybookとFigmaの連携機能によるデザインと実装したコンポーネントの紐づけ
- StorybookでのVRTとインタラクションテストの強化
- 汎用コンポーネントだけでなく、ドメインに紐づくコンポーネントのデザインシステム化
- Radix Primitivesのアクセシビリティ配慮にとどまらず、TVerや動画配信サービスとして必要なアクセシビリティの改善
今回紹介した取り組みは始まったばかりですが、チームとしてTVerのフロントエンド開発の生産性と品質向上に取り組んでいます。もしこのような技術的チャレンジに興味を持っていただけましたら、ぜひ採用サイトから応募してみてください!