当社はスラッシュのリブランディングを シリーズB資金調達そして、ブランドと並行して製品のビジュアル刷新を図りたいと考えました。UIの更新と並行して、過去3年間の急成長で蓄積した技術的負債の多くに対処する絶好の機会であるとすぐに気づきました。
したがって、プロジェクト フェイスリフト 誕生した——スケーリング事業とチームのためのSlashフロントエンド基盤を築く機会が訪れた。過去の過ちから学び、時代を超えて通用する決断を下すことを目指した。
第1部:技術的負債への対処
このセクションでは、「負債」の各ポイントについて説明します。後述するパート2では、各ポイントに対する当社の解決策について詳しく解説します。
エモーション: CSS in JS ランタイムのボトルネック
当社のオリジナルデザインシステムは、その上に構築された 感情 そしてそれは我々に大いに役立った。CSS in JSライブラリの主な利点は、コンポーネントのマークアップとスタイリングを同一場所に配置できることで、これにより反復作業が極めて迅速になった。しかし、顧客が拡大しデータ量がますます増加するにつれ、emotionでパフォーマンスのボトルネックが発生し始めた。CSS-in-JSは実行時のオーバーヘッドを必要とし、メインのイベントループを遅延させる可能性がある。 大規模リストの仮想化や高速スクロール時に顕著です。コンポーネントの高速マウント/アンマウント時、動的スタイル計算とスタイルシート生成がブラウザに負荷をかけ、60fpsでのレンダリングが不可能になるケースがありました。他社では 同様のパフォーマンスのボトルネックを発見した そして、実行時のオーバーヘッドに依存するソリューションから離れた。
プロップベースの「神のコンポーネント」
「神は反対するこれはOOPに由来する概念で、単一のオブジェクトが単独で過大な責任を負うことを指します。私たちはReactコンポーネントでも同様の現象が発生していることに気づき、これを「ゴッドコンポーネント」と呼び始めました。
当社のReactオリジナルコンポーネントの多くは、プロップスベースのインターフェースで記述されています。これはデータとコールバックを提供し、単一のコンポーネントをレンダリングする方式です。表面的には洗練されたインターフェースを提供します:単にデータとプロップスを提供し、マークアップとビジネスロジックのすべてをコンポーネントに任せればよいのです。
実際には、コードベースが拡大するにつれ、これらのコンポーネントは当初想定されていなかった用途にまで拡張されていきます。 エンジニアが新たな設定を必要とするたびにプロパティを追加した結果、時間の経過とともに保守性と使用性が悪化していった。こうしたコンポーネントは最終的に「ゴッドコンポーネント」と化し、特定の課題を解決するための単発プロパティや、コンポーネント本体に追加マークアップを注入/置換するための「エスケープハッチ」(レンダリングプロパティパターンに類似)など、維持管理に特別な配慮を要する状態となった。具体的なコード例は後ほどブログで示す。
不整合なフォーム管理
当初のフォーム管理ソリューションは単純なReactの状態管理でした。ローカル状態は扱いやすい反面、標準化や深い型安全性に欠け、コード品質のばらつきを招いていました。エンジニアごとにローカル状態の実装方法が微妙に異なり、ある者は... コンテキストAPI 1つの大きな共有フォーム状態を持つものもあれば、フィールドを個別に分割するものもある useState 呼び出し
これらの形式を拡張する段階になると、しばしばメタデータ状態が フィールドがタッチされたか 追加されることになり、フォーム操作を処理するために明示的に定義されたローカル状態が多数存在する、標準化されるべき煩雑なコンポーネントが生成される。
第2部:実行
コードベースの現状の課題点を概説したところで、各解決策とその背景にある意思決定について深く掘り下げていきます。
Tailwind v4.0 への移行
スタイリングソリューションを評価した結果、最終候補は2つ残りました:Tailwind v4.0と パンダCSS両者とも実行時のオーバーヘッドがゼロであり、コンポーネントのマークアップとスタイリングの共存を可能にした。PandaCSSの利点は型安全性と学習曲線の低さであり、既存のデザインシステムと非常に似通っていたためである。
Tailwindの利点は成熟したエコシステムと開発者の親しみやすさでした。多くのエンジニアが個人プロジェクトでTailwindを使用しており、私を含め非常に気に入っていました。Tailwindを組み込みサポートする優れたツールには以下のようなものがあります: 超新星 そして テイルウィンドのバリエーションAIコーディングツールは、Tailwindのクラス名を最初から正確に生成する点でも優れており、移行作業を大幅に容易にします。
実行時のオーバーヘッドがないことのパフォーマンス上の利点を可視化するため、EmotionとTailwindにおける同一操作(大規模な仮想化されたテーブルビューの高速スクロール)の前後のフレームチャートを以下に示す:


フィグマからコードへのパイプライン
Tailwindへの移行にあたり、Figmaを真実の源として、Figmaトークンとコードベースを同期させる方法を模索しました。Figma上のデザインシステムはデザインエージェンシーによって管理されています。 メタカーボンデザイン上の決定を行い、意味論的トークンを用いてスタイルシートを作成する人物。
私たちの目標は、基盤となるデザインシステムの変更を手動でレビューし、最小限の労力で実装できるパイプラインを構築することでした。Figmaからデザイントークンを抽出してコードに反映させるため、優れたTailwind v4.0からコードへの抽出機能を備えたSupernovaを選択しました。
CSS出力の作業中に、興味深い問題に遭遇しました: セマンティックトークンが、適用されるプロパティに応じて異なる値を指すようにしたかった。 以下に具体例を示します:
私たちは望んでいます ニュートラル・控えめ・デフォルト 3つの異なる色合いに分解する 軽製品中立: 100, 500 そして 1000 - ただし、これだけを使用した場合、Tailwind v4.0 --色 テーマ変数 スポーンする 背景-背景-中立-控えめ-デフォルト, 境界線背景色中立微妙なデフォルト, 背景テキスト中立控えめデフォルト そして、意味をなさない上に命名において冗長な、その他多くのクラス名。
したがって、出力結果を変換し、Tailwind v4.0のより具体的な機能を活用できるようにするカスタムビルドスクリプトを作成しました。 テーマ変数名前空間:
これはclassNamesとして次のように解決されます 背景色-中立-控えめ-デフォルト, 境界線中立・控えめ・デフォルト そして テキスト中立・控えめ・デフォルト - それらはすべて、それぞれの異なる色合いに帰着する。
したがって、基盤となるデザインシステムが変更される際には、Supernovaエクスポーターを実行し、その出力をコードベースに投入してレビューするだけで済みます。その後、ビルドスクリプトが自動的に生の出力をビルド済みCSSファイルに変換し、すべてのパッケージがこれを消費します。
At Slash, we follow a philosophy of maintaining control over our tooling . This allows us to extend functionality quickly and prevent vendor lock in. It also helps us diagnose and fix issues directly. By having our build script, we aren’t locked into Supernova as a vendor, and can extend it - like adding support for divide to be the same color as border, or light vs dark mode themes.
Other examples include our query builder and JSON schema to typescript code generation pipeline.
ヘッドレスUIライブラリ:Base UIとReact Aria
まず、なぜスタイリング済みのソリューションではなく、ヘッドレスUIライブラリを必要としたのかを説明します。今回の刷新の目的は、製品を通じてブランドアイデンティティを明確に表現することにあります。ShadcnやMaterialUIのような事前スタイリング済みのソリューションを使用すると、コンポーネントが事前にスタイリングされてしまうため、この目的が達成できなくなります。
リファイン前はRadix UIを使用していましたが、私たちは その将来について確信しているコアメンテナがBase UIに移行する様子を見てきた。Base UIはシンプルで拡張性が高く、アクセシビリティへの対応も優れており、我々のニーズに非常に良く適合するライブラリとして台頭してきた。優れたメンテナ陣を擁するBase UIは、Slashのニーズと共に成長していく将来性について確信を与えてくれる。
Base UIのサポートがまだない特定のコンポーネント(例:日付ピッカー)については、ヘッドレスUIの候補として次点だったReact Ariaを採用しました。 React Ariaは実戦で検証済みであり、あらゆるアクセシビリティ基準を厳格に遵守しています。最終的にBase UIを基盤としてコンポーネントを構築したのは、React Ariaのアプローチが非常に独自性が高く、そのエコシステムへの完全な依存を必要とするためです。私たちはより少ない精神的負荷で運用できるソリューションを優先しました。
複合部品
前述の通り、プロパティベースのコンポーネントは当初は洗練されているが、拡張性に欠けるという問題を抱えている。より拡張性の高いコンポーネントを作成するための我々の解決策は、 複合コンポーネントアーキテクチャこれはコンポーネントのマークアップ制御を親コンポーネントに引き渡すものです。両者の違いを最もよく示す方法は例です。以下に、検索可能なセレクトコンポーネントの旧版と新版を簡略化したバージョンを示します:
そしてこちらが、複合コンポーネントアーキテクチャを使用した新しい検索可能なセレクトコンポーネントです:
一見すると、複合コンポーネントは扱いにくいように見えます。開発者はマークアップのパターンを理解し、それに従う必要があります。その利点は、マークアップを拡張する柔軟性にあります。親コンポーネントが完全な制御権を持つためです。そのため、マークアップのハックがコアコンポーネントに混入することはありません。エンジニアがselect要素にフッターを追加する必要がある場合?マークアップに追加するだけです。 フッターのレンダリング選択 コアコンポーネントへのプロパティ!フッターが共通要件となった場合、作成する <SearchableSelect.Footer /> コンポーネント!
複合コンポーネントのもう一つのよくある落とし穴は、エンジニアがコアコンポーネントに含まれていない共通パターンの実装を重複して作成してしまうことです。そのため、私たちのチームは共通パターンをより意識し、必要に応じてコアコンポーネントに追加する必要があります。また、複合コンポーネントをワンラインコンポーネントの基盤となるAPIとして使用することもできますが、これらのコンポーネントがゴッドコンポーネント化しないよう、非常に意図的にこれを行っています。 ストーリーブックを介した知識共有の具体的な手法については、後述のセクションで詳しく説明します。
テイルウィンドのバリエーション
コンポーネントの実際のスタイリングにおいては、私たちは主に テイルウィンドのバリエーション私たちの中には使ったことがある者もいます クラス分散権限 (CVA) であり、当社のスタイリングソリューションにもそのようなバリアントロジックを望んでいました。Tailwind Variantsには 採用を決めた決め手となったいくつかの機能スロットAPIと組み込みの競合解決機能が主なものです。
結局のところ、必要なのはスタイリングのバリエーションコードを整理整頓し、複合コンポーネントアプローチをカバーできる拡張性を備えたソリューションでした。Tailwind Variantsの採用により、プロパティに基づいてemotion-styled divに異なるトークンを明示的に適用していた従来の条件付きスタイリングコードが大幅に標準化されました。
以下に、ボタンの実装と使用方法の例を示します:
ご覧の通り、呼び出し元は ボタン 実際には使用する必要がない ボタンバリエーション - プロパティを渡します ボタン 設定オプションとして、その後呼び出しに使用される ボタンバリエーション 内部的に。また、我々は 延長する 類似したコンポーネント(入力フィールドや選択フィールドなど)間でスタイルを共有する機能。
また、セマンティックなクラス名のおかげでスタイルをスキャンするのがいかに簡単かお気づきになるでしょう。
テストとドキュメント作成: Storybook + Chromatic
プロジェクト前半は単独で作業し、テストやドキュメント作成を本来あるべきほど重視していませんでした。しかし、基盤となるコンポーネントライブラリが完成し、各エンジニアが担当製品の改修に加わるにつれ、「このコンポーネントは存在するのか?」「このコンポーネントを正しく使用するには?」といった非常に単純な質問に答えるための集中管理場所が必要であることが明らかになってきました。
新入エンジニアのサムが、単身でStorybookとChromaticのテストスイートを構築しました。これらがなければ、特に複合コンポーネントの習得曲線を考慮すると、コンポーネントの誤用という技術的負債の罠に陥っていたでしょう。
Storybookは、すべてのコンポーネントの使用例を一元管理することを可能にし、コード例を用いてそれらの疑問を完全に解決します。これにより、エンジニアがどのコンポーネントを使用すべきか、また必要な設定オプションをサポートしているかどうかが即座に明らかになります。
Chromaticは視覚的な後退を検出します。これにより、基盤となるコンポーネントへの変更が視覚的要素に影響を与える場合、即座にフラグが立てられ、手動レビューが行われるまでマージがブロックされます。
フォーム管理:ゾッド対アークタイプ
Slashのコードベースは完全にTypeScriptで記述されており、ビルド時にコードベース全体で共有型を生成することでこの特性を最大限に活用しています。開発者が誤った操作で自ら足を引っ張ることを困難にし、当社の広範な型安全性を活用できるフォーム管理ソリューションを求めていました。
最終的にArkTypeを選択しました。Typescriptとの結合が非常に強固だったため、いかなる程度のドリフトも防ぐことができたからです。生成された型をArkTypeの型と1対1で結びつけることが可能でした。 満たすつまり、基盤となる型が変更されると、コードベースは型エラーを発生させる。
以下に具体例を示します:
ZodやReact Hook Formも有用ではあったものの、TypeScriptの利点を直接活かせるソリューションを求めており、ArkTypeが明らかに優位でした。React Hook FormではなくTanstack Formを選んだのは、TypeScriptへの準拠性がより厳格だったためです。
Tanstackフォームの特徴として、厳格で独自の形式構造を強制する点が挙げられます。学習曲線は生じますが、コードベース全体でフォーム記述の標準化を徹底する価値があると判断しました。
実装:機能フラグとコード分岐
Faceliftの実際のコード記述にあたっては、旧デザインシステムの新機能と並行してFaceliftをリリースしていたため、巨大なプルリクエストを1つ作成することを避けたいと考えていました。
私たちは使用した 機能フラグそして単に機能フラグを有効にした 当社アカウントのみこれにより、私たちは 自社製品 独自の変更を加えました。また、フェイスリフトのオン/オフを切り替えるシンプルなツールバーも実装し、フェイスリフトを素早くオン/オフすることで、後方互換性の問題(リグレッション)を非常に簡単に発見できるようにしました。

実際のコード分岐については、ビジネスロジックコードの重複を避けることを目指し、次のように実施しました:
上記の例は理想的なケースを示しています - 既存のコードベースに大量の新規コードを導入する現実には、はるかに厄介な問題が伴う。例えば:もしあなたが フェイスリフトである 上位レベル(例えばトップページレベル)でスイッチ処理を行う場合、そのコンポーネントの下位にある全ての分岐に対してビジネスロジックを重複して実装せざるを得ない。しかし個々のコンポーネントレベルでチェックを実行すると、親コンポーネントの古いマークアップロジックに依存することになり、いずれにせよ親コンポーネントの改修が必要になるケースが多い。
生産上の問題から私たちを守ったのは、この機能をすべて機能フラグの背後に隠すことで、実際の顧客にとっては、 フェイスリフトである いつも 偽 準備が整うまで。内部的には、プラットフォームに回帰が混入した際に検出できるよう、これを有効にしておく。
機能実装が完了した段階で、当社は緊密な関係にある顧客と連携し、機能を有効化(無効化オプション付き)するとともに、既存ワークフローを妨げることなくフィードバックを求め、リグレッションを特定しました。
実行に関する注意事項
上記のセクションは非常に整然と構成されていますが、実装順序は決してこの通りではありませんでした。実際には、試行錯誤と迅速な反復によってこれらの決定がなされました。例えば、
- CSSビルドスクリプトの最初の草案がスパムを撒き散らした
@ユーティリティ各カスタムクラス名を作成するための指示 - その後サムはこれを基に構築し、これを見つけた GitHubディスカッション テールウィンドのテーマ変数名前空間の概要を示した。 - 現在のアプローチに落ち着く前に、検索可能なシングルセレクトコンポーネントには異なるBaseUIコンポーネントを使用した2つの反復版が存在した。 メニューAPI.
つまり、私たちがこれらの決断を下した方法は、上記のトレードオフのように洗練されたものではなかったということです。共通点は、最初から完璧に仕上げなければならないという思考の麻痺に陥ることなく、絶え間ない反復を重ねたことです。
第3部:学び
私は個人的に、フロントエンド全体をカバーするプロジェクトから多くのことを学びました。私たちのチームも同様に多くのことを学びました。特に、より多くのエンジニアがそれぞれの製品の刷新に貢献するにつれて、その学びは深まりました。
「底上げ」
技術的負債が蓄積した主な原因は、知識の共有不足にあった。スタートアップ初期の段階では、全員が密接に連携しているため問題にはならない。しかしチームが拡大し製品の範囲が広がるにつれ、前提条件や使用パターンを以前と同じ速さで共有することは不可能になる。
今回の改修における私たちの哲学は「基準を引き上げる」ことでした。質の低いフロントエンドコードが書けない環境を構築するため、優れたフロントエンドコードの定義を明文化し、本番環境への影響前にエラーを捕捉します。この実現にStorybookとChromaticは不可欠であり、導入投資に見合う価値があります。基準引き上げに向けたあらゆる努力は、コードベース全体の品質向上に複利効果をもたらします。その逆もまた真なりです。
長期プロジェクトの実態
このリニューアルは開始から完了まで約4ヶ月間の継続的な作業を要し、最終段階では3週間にわたる専用の「ハックウィーク」を実施。この期間には他の6名のエンジニアもリニューアル作業に貢献してくれました(彼らには心から感謝しています<3)。また、プロジェクト全体の範囲設定、Metacarbonとの連携、新たなUXパターンのフィードバック提供において、優秀なPM(アンディ)のサポートも得られました。
この改修は段階的にリリースできるプロジェクトではなかったため、長期間にわたり顧客へ製品をリリースする喜びを味わえませんでした。そこで学んだのは、内部でコンテキストを切り替える手法です。コアコンポーネントライブラリの開発と、そのコアコンポーネントをフルページに適用する作業を交互に繰り返すのです。この手法はコアコンポーネント使用時のエッジケースを素早く発見する助けにもなり、抽象化の境界線を再構築するきっかけとなります。
顧客からの評価も得られないまま大量のコードをリファクタリングするのは、やる気を失わせることもある。それでも私がモチベーションを維持できたのは、いくつかの理由があったからだ:
- スラッシュの仲間たちのおかげで、毎日仕事に来るのがまだ楽しかった!
- 進捗を社内で自慢することはまだできる。誰かに自分の進捗を見せられることで得られるドーパミンは、モチベーションを維持する素晴らしい方法だ。特に自分の仕事に情熱を持っているならなおさらだ。
- このプロジェクトは、今後長きにわたりフロントエンドのコード基盤となるはずだったため、正しく構築する価値があった。
- スラッシュは成長し勝利を収めている。それが 全体的に 動機。
第4部:次は何が待っているのか
フェイスリフトの導入が順調に進む中、我々の最優先課題はエッジケースと回帰バグの捕捉となります。幸いにも、世界トップクラスのサポートチームが顧客の移行を支援し、問題を瞬時に表面化させてくれます。
当社の作品をご覧になりたい場合は、こちらでご覧いただけます。 デモサイトまた、Slashが提供する機能もチェックできます!
ここまでの内容をご覧になり、急成長中のスタートアップで実際の顧客の問題解決に取り組む意欲のある優秀なエンジニアの方は、ぜひご応募ください。 ここご連絡をお待ちしております。