本文へスキップ
Starone

ケイパビリティ

高速でアクセシブル、保守しやすいWebアプリケーションのためのフロントエンドアーキテクチャ

ブラウザは、システムの中で最も制御の効かない実行環境です。端末は不明、ネットワークは不安定、入力は信頼できません。私たちはフロントエンドをスタイリングの作業ではなくアーキテクチャの問題として捉え、コードをどこでレンダリングするか、状態をどこに置くか、インターフェースをどのように縮退させるかを設計します。

概要

フロントエンドエンジニアリングとは、チームの誰も制御できないハードウェアとネットワークの上で実行される部分を設計することです。混雑したモバイル回線につながったミドルレンジのAndroid端末は、開発者のノートPCに比べて数倍遅くJavaScriptをパースします。そのため、どれだけのコードを配信するか、HTMLをどこで生成するか、ページがいつ操作可能になるか、クリックから結果までに何往復の通信が挟まるかといったアーキテクチャ上の選択は、すべてユーザーの体感レイテンシに直接跳ね返ります。

同じ選択が、コードベースとチームをどこまで拡大できるかも決めます。モジュール境界も、型付けされたAPIコントラクトも、共有コンポーネントライブラリもないフロントエンドは、単一のデプロイ単位となり、あらゆる変更が別の箇所でのリグレッションを招くおそれを抱え、エンジニアを増やすほどデリバリーは遅くなります。機能間の明示的な境界、バージョン管理されたトークンとコンポーネントを備えたデザインシステム、自動生成されたAPI型があれば、複数のチームがリリースのたびに調整することなく並行して出荷できます。

私たちはTypeScriptを用い、ReactとNext.js、またはVueとNuxtで開発します。レンダリング戦略、状態の所有者、パフォーマンスバジェット、アクセシビリティ、ブラウザセキュリティは、ルート単位・機能単位で下す第一級の設計判断として扱います。それぞれの判断を記録し、パイプラインで計測し、本番環境では実ユーザーのデータで観測します。

エンジニアリング領域

技術領域

01

コンポーネントアーキテクチャとデザインシステム

コンポーネントは、表示専用のプリミティブ、それらを組み合わせたパターン、自身のデータアクセスを持つ機能モジュールに分割し、依存は一方向にのみ許可します。色、余白、タイポグラフィ、モーションのデザイントークンを唯一の信頼できる情報源とし、CSSカスタムプロパティとして出力することで、コンポーネントのコードに触れずにテーマやブランドを切り替えられます。インポートパスに対するLintルールで境界を強制するため、チームが拡大しても境界は保たれます。

  • デザイントークンとCSSカスタムプロパティ
  • ヘッドレスでアクセシブルなコンポーネントプリミティブ
  • コンポーネントの作業台兼ドキュメントとしてのStorybook
  • Feature-Slicedなモジュール境界
  • strictモードのTypeScript
  • ビジュアルリグレッションテスト
02

状態管理とAPI連携

サーバーステートはバックエンドが所有するデータのキャッシュであり、フェッチ、重複排除、無効化、リトライが必要です。クライアントステートは、開いているパネル、下書き、選択状態といった一時的なUIの状態です。両者を1つのグローバルストアで扱うことが、古いデータの表示や不要な再レンダリングの最も一般的な原因です。私たちはサーバーステートをクエリキャッシュに、共有可能な状態をURLに置き、クライアントストアは残ったものだけに使います。

  • サーバーステート向けのTanStack QueryとSWR
  • クライアントステート向けのZustand、Redux Toolkit、Pinia
  • 状態としてのURLクエリパラメータ
  • OpenAPIとGraphQLからの型生成
  • ロールバック付きの楽観的更新
  • リクエストのキャンセルと重複排除
03

レンダリング戦略

サーバーサイドレンダリング、静的生成、クライアントサイドレンダリング、ストリーミングは、最初の1バイトまでの時間(TTFB)、操作可能になるまでの時間、キャッシュのしやすさ、インフラコストのトレードオフがそれぞれ異なります。どれを選ぶかは、コンテンツにどの程度のパーソナライズと鮮度が求められるかに応じてルートごとに決めます。ハイドレーションのコストはバジェットとして管理し、サーバーコンポーネント、アイランド、遅延ハイドレーションで削減します。

  • サーバーサイドレンダリング(SSR)
  • 静的生成とインクリメンタル再生成
  • Suspense境界を用いたストリーミングSSR
  • React Server Components
  • シングルページアプリケーションのシェル
  • 選択的ハイドレーションと遅延ハイドレーション
  • CDNとエッジでのキャッシュ
04

パフォーマンスとCore Web Vitals

Core Web Vitalsは、読み込み(Largest Contentful Paint)、応答性(Interaction to Next Paint)、視覚的安定性(Cumulative Layout Shift)を、実ユーザーの75パーセンタイルで計測する指標です。公開されている「良好」のしきい値は、それぞれ2.5秒、200ミリ秒、0.1です。ラボ環境のスコアは低速な端末の実態を表さないため、私たちはバンドルサイズとロングタスクにバジェットを設定してCIで強制し、フィールドデータで検証します。

  • ルート単位・コンポーネント単位のコード分割
  • AVIFとWebPによるレスポンシブ画像
  • フォントのサブセット化とfont-displayの制御
  • Priority Hintsとリソースのプリロード
  • ロングタスクの分割とメインスレッドのスケジューリング
  • Lighthouse CIによるバジェット管理
  • リアルユーザーモニタリング
05

アクセシビリティとレスポンシブアーキテクチャ

アクセシビリティは、完成したページを監査して後付けするとコストがかさむため、低コストで対応できるコンポーネントライブラリの段階で組み込みます。目標はWCAG 2.2のレベルAAです。まずセマンティックなHTMLを使い、ARIAはネイティブのセマンティクスで足りない箇所に限定し、キーボードだけで完全に操作できるようにしたうえで、ダイアログやルート遷移ではフォーカスを管理します。レイアウトは固定のデバイス幅の一覧ではなく、コンテナのサイズと入力方式に応じて変化させます。

  • セマンティックHTMLとランドマーク構造
  • キーボードナビゲーションとフォーカス管理
  • NVDAとVoiceOverによるスクリーンリーダーテスト
  • axe-coreによる自動チェック
  • コンテナクエリと流動的なタイプスケール
  • モーション軽減設定と色コントラストへの対応
06

セキュリティ、PWA、リアルタイムインターフェース

ブラウザ内にあるものはすべてユーザーから見え、ページ上で実行されるあらゆるスクリプトから到達できます。そのため、フロントエンドが持つ権限は可能な限り小さく設計します。Service Workerは、プロダクトが必要とする場合にオフラインキャッシュ、インストール可能性、バックグラウンド同期を提供します。ライブデータは、流れが一方向であればServer-Sent Eventsで、クライアント側からも低レイテンシで送信する必要があればWebSocketで受け取ります。

  • nonceベースのContent Security Policy
  • XSS対策としての出力エンコーディングとTrusted Types
  • HttpOnly、Secure、SameSite属性付きのセッションCookie
  • Subresource Integrityと依存関係の監査
  • Service WorkerとWorkboxのキャッシュ戦略
  • WebSocketとServer-Sent Events

ルートごとのレンダリング戦略の選択

レンダリングはアプリケーション全体で1つに決める設定ではありません。同じプロダクトの中でも、マーケティングページ、商品一覧、認証後のダッシュボード、ドキュメントエディタでは、鮮度、パーソナライズ、インタラクティブ性の要件が異なり、Next.jsやNuxtといったフレームワークではルートごとに方式を選べます。判断の軸は2つです。そのHTMLをユーザー間で共有できるか、そしてどの程度古くなることを許容できるかです。

ハイドレーションは、サーバーレンダリングの隠れたコストです。サーバーがHTMLを送信した後、クライアントは同じコンポーネントツリーをダウンロードして実行し、イベントハンドラを接続します。それが完了するまで、ページは準備ができたように見えても反応しません。サーバーコンポーネントは、インタラクティブでないコンポーネントをクライアントバンドルから完全に取り除きます。ストリーミングは、シェルを即座に送信し、遅いデータはSuspense境界の背後で解決するため、1つの遅いクエリがレスポンス全体を遅らせることがなくなります。

  • 静的生成:ドキュメントやマーケティングページなど、全ユーザーに同一で公開時にのみ変わるコンテンツ向け。CDNから配信され、レイテンシとコストが最も低い
  • インクリメンタル再生成:全体の再ビルドに時間がかかりすぎ、数秒から数分の古さを許容できる大規模カタログ向け
  • SSR:インデックス可能なHTMLと高速な初回描画が必要な、パーソナライズされたページやリクエスト依存のページ向け。リクエストごとにサーバーのコンピュートコストが発生する
  • ストリーミングSSR:高速なシェルと低速なデータソースを組み合わせるページ向け
  • SPA:検索インデックスが不要でセッションが長く、重い初回ロードを償却できる、認証後の高インタラクティブなツール向け

アーキテクチャ

フロントエンドのレンダリング戦略

ビルド時(SSG)

  1. 01

    コンテンツとデータ

    ビルド時にCMS、Markdown、APIを読む

  2. 02

    静的生成

    ページを一度だけHTMLにレンダリング

  3. 03

    CDN

    キャッシュしてエッジから配信

  4. 04

    再検証

    変更時または一定間隔で再生成

リクエスト時(SSR)

  1. 01

    リクエスト

    ユーザー別または最新のデータが必要

  2. 02

    サーバーレンダリング

    データを取得しコンポーネントを描画

  3. 03

    ストリーミング

    シェルを先に送り、遅い部分は後続

  4. 04

    ハイドレーション

    ブラウザでハンドラーをアタッチ

クライアント(SPA)

  1. 01

    クライアント状態

    UI状態と楽観的更新

  2. 02

    API呼び出し

    バックエンドへの型付きリクエスト

  3. 03

    キャッシュ

    重複排除、再検証、無効化

  4. 04

    ナビゲーション

    プリフェッチ付きクライアントルーティング

ページをどこでレンダリングするかによって、速度、鮮度、コストが決まります。多くのプロダクトは3つを併用します。安定したコンテンツには静的生成、パーソナライズされたデータや最新データにはサーバーレンダリング、インタラクションにはクライアント状態を使います。

サーバーステートとクライアントステートは別の問題

アプリケーションが表示するものの大半は、アプリケーション自身の状態ではありません。それはバックエンドのデータのスナップショットであり、いつでも古くなる可能性があり、複数のコンポーネントから同時に要求されることがあり、ミューテーションの後には再取得が必要です。クエリキャッシュはこれをそのままモデル化します。各リソースがキー、鮮度の有効期間、リトライポリシー、無効化ルールを持ち、コンポーネントはデータをストアにコピーする代わりにキーを購読します。

サーバーステートをグローバルストアから切り離すと、残るものは通常わずかです。フォームの下書き、ウィザードの進行状況、UIの設定などです。フィルター、ソート順、ページネーション、選択中のタブはURLに置くべきものです。そうすることで、画面をリンクとして共有でき、リロード時に復元され、戻るボタンも正しく動作します。専用のクライアントストアが正当化されるのは、キャンバスエディタや取引の注文入力画面のように、離れたコンポーネント間で共有され、かつ高頻度で変化する状態です。

フロントエンドアーキテクチャがチームの拡大を制約し、また可能にする仕組み

フロントエンドがチームとともにスケールするのは、変更が局所にとどまる場合だけです。そのためには、公開インターフェースを持つ機能モジュール、機能同士が互いの内部をインポートしないというルール、そしてバージョン管理され所有者が明確な、デザインシステムのコンポーネントとAPIクライアントからなる共有レイヤーが必要です。モノレポでは、TurborepoやNxといったツールが依存グラフを用いて、変更の影響を受ける部分だけをビルド・テストするため、コードベースが大きくなってもパイプラインの所要時間はほぼ一定に保たれます。

マイクロフロントエンドはこれをさらに推し進め、各チームに独立してデプロイできるスライスを与え、Module Federationやエッジで合成します。これが解決するのは、多数のチームにまたがる独立したリリースサイクルという組織上の問題であり、その代償として依存関係の重複、一貫性の維持の難しさ、ランタイムの複雑化を伴います。ほとんどのプロダクトでは、境界を強制したモジュラーモノリス型のフロントエンドにより、1つのビルドと1つのデプロイで同等の分離が得られます。

スケールを制約するもう1つの要素がAPIコントラクトです。OpenAPIドキュメントやGraphQLスキーマから生成した型は、バックエンドの破壊的変更を本番環境での実行時エラーではなく、パイプライン上のコンパイルエラーに変えます。さらに、あらゆるコンポーネントから防御的なコードの一群を取り除けます。

エンジニアリングの進め方

私たちのアプローチ

私たちは、プロダクトを使う人々とそれを保守するチームの制約を起点とし、後から覆すとコストが高くつく判断を、画面を1つも作る前に確定させます。

  1. 01

    制約とバジェットの確立

    対象端末、ネットワーク条件、インデックス要件、アクセシビリティ上の義務、ブラウザサポートを特定し、それらをルートごとのJavaScript量、Core Web Vitals、インタラクションのレイテンシに関する数値バジェットへと落とし込みます。

  2. 02

    ルートごとのレンダリング方式とデータ所有者の決定

    各ルートにレンダリング戦略とキャッシュポリシーを割り当て、各状態にはサーバーキャッシュ、URL、コンポーネント、クライアントストアのいずれかを所有者として割り当てます。セッションモデルとトークンの扱いも、この時点でバックエンドと合意します。

  3. 03

    デザインシステムを先に構築

    機能開発の前に、トークンとアクセシブルなプリミティブを実装し、Storybookでドキュメント化します。キーボード操作、フォーカス処理、コントラストはコンポーネントレベルで一度検証し、すべての画面がそれを継承するようにします。

  4. 04

    型付きコントラクトに基づく機能実装

    機能モジュールは生成されたAPI型とモックサーバーを利用するため、フロントエンドとバックエンドは並行して開発を進められます。ユニットテストでロジックを、コンポーネントテストで振る舞いを、Playwrightで重要なユーザージャーニーをエンドツーエンドで検証します。

  5. 05

    パイプラインでの品質の強制

    すべてのプルリクエストで、プレビューデプロイに対して型チェック、Lint、アクセシビリティチェック、バンドルサイズの比較、Lighthouseのバジェット検証を実行します。バジェットを超える変更は、リリース後に発覚するのではなく、ビルドの失敗として検出されます。

  6. 06

    本番環境での計測と改善

    リアルユーザーモニタリングにより、Core Web Vitals、JavaScriptエラー、インタラクションのトレースを、ルート、端末クラス、リリースごとに把握します。リグレッションは特定のデプロイまで遡って原因を特定し、フィールドデータに基づいて修正します。

トレードオフ

設計を左右する判断

アーキテクチャを確定する前に、お客様のチームとともに検討する問いです。

このアプリケーションはサーバーレンダリングにすべきか、シングルページアプリケーションにすべきか
ページをインデックスさせる必要がある場合、プレビュー付きのリンクとして共有される場合、低速な端末で素早く描画する必要がある場合は、サーバーのコンピュートコストとハイドレーションのコストを受け入れたうえでサーバーレンダリングを選びます。セッションが長く操作の多い認証後のツールにはSPAを選びます。初回ロードの重さは一度支払えば済み、CDN上の静的なシェルは運用も容易です。多くのプロダクトでは両方が必要になり、ルートごとに使い分けます。
ReactとNext.jsか、VueとNuxtか
どちらのエコシステムもSSR、静的生成、ストリーミング、型付きのデータ取得に対応しているため、判断は主にチームの事情で決まります。Reactは採用市場とライブラリのエコシステムが大きく、サーバーコンポーネントのモデルもより成熟しています。VueはAPIの表面積が小さく、きめ細かなリアクティビティにより手動のメモ化が少なくて済みます。両者の技術的な差よりも、チームの既存スキルと既存のコードベースのほうが重要です。
そもそもグローバルな状態管理ライブラリは必要か
たいていは想定より少なくて済みます。サーバーのデータはクエリキャッシュに、ビューの状態はURLに、ローカルな状態はコンポーネントに置き、そのうえで何が残るかを確認します。状態が広く共有され、頻繁に更新され、開発者ツールやタイムトラベルデバッグが必要な場合に、Zustand、Redux Toolkit、Piniaを追加します。Redux Toolkitの厳格な構造が効果を発揮するのは、主に大規模なチームです。
ブラウザは認証トークンをどこに保持すべきか
トークンをサーバー側で保持し、ブラウザにはHttpOnly、Secure、SameSite属性付きのCookieを渡すBFF(Backend for Frontend)を優先し、状態を変更するリクエストにはCSRF対策を施します。localStorageに置いたトークンはページ上で動くあらゆるスクリプトから読み取れるため、XSSの欠陥や侵害された依存パッケージが1つあるだけで漏洩します。どうしてもブラウザにトークンを置く必要がある場合は、メモリ上に保持し、有効期間を短くし、リフレッシュトークンをローテーションします。
モノリシックなフロントエンドか、モノレポか、マイクロフロントエンドか
まずはモジュール境界を強制した1つのアプリケーションから始めます。複数のアプリケーションがデザインシステムやAPIクライアントを共有するようになったら、共有パッケージを持つモノレポへ移行します。マイクロフロントエンドを採用するのは、独立したチームが共通のリリーストレインに阻まれている場合に限ります。ランタイムでの合成、バージョンのずれ、ペイロードの重複は恒久的なコストになるためです。
ライブデータにはWebSocket、Server-Sent Events、ポーリングのどれを使うか
通知、進捗、AIのトークン単位の出力など、サーバーからクライアントへのストリームにはServer-Sent Eventsを使います。通常のHTTP上で動作し、自動的に再接続し、プロキシも通過しやすいためです。共同編集や取引のように、クライアント側からも頻繁にメッセージを送る場合はWebSocketを使います。更新頻度が低く、数秒の遅延が許容できる場合は、条件付きリクエストを用いたポーリングが今でも適切な選択です。

本番対応

本番運用に耐える条件

セキュリティの確保

nonceベースの厳格なContent Security Policy、フレームワークによる出力エンコーディング、Trusted Typesによってクロスサイトスクリプティングの影響を抑え、セッションの認証情報はスクリプトから届かないHttpOnly Cookieに保持します。依存関係はロックファイルで固定してCIで監査し、サードパーティのオリジンから配信する場合はSubresource Integrityを付けて読み込みます。認可は常にAPI側で強制します。UI上でコントロールを隠すことはユーザビリティ上の措置であり、セキュリティ対策ではありません。

スケーリング

静的アセットはコンテンツハッシュを付与してimmutableとしてキャッシュし、CDNから配信するため、トラフィックの増加の大半はオリジンではなくエッジで吸収されます。サーバーレンダリングするルートはステートレスなプロセスとして水平にスケールし、Cache-Controlとstale-while-revalidateをルートごとに調整します。コードベースの拡大には、モジュール境界、影響範囲のみのビルド、リモートビルドキャッシュで対応します。

監視

リアルユーザーモニタリングでLCP、INP、CLSを記録し、原因となった要素やインタラクションを特定できる形で、ルート、端末、リリースごとに分類します。エラーは非公開でアップロードしたソースマップとともに収集し、ブラウザのスパンはOpenTelemetryを通じてバックエンドのトレースへ伝播させるため、遅いインタラクションをその原因となったAPIまで追跡できます。

デプロイ

すべてのプルリクエストで独立したプレビューデプロイを生成し、その上で自動チェックと手動チェックを実施します。リリースはアトミックかつイミュータブルであるため、ロールバックは直前のビルドへポインタを切り替えるだけで済み、フィーチャーフラグによってデプロイとリリースを分離します。ロールアウト中も旧バージョンのアセットを利用可能な状態に保ち、開いたままのセッションがチャンクの欠落で失敗しないようにします。

システム連携

フロントエンドは、生成されたクライアントを通じてRESTまたはGraphQLのAPIを利用し、OpenID Connectで認証し、SSEまたはWebSocketでライブデータを受け取ります。汎用APIでは呼び出し回数が多くなりすぎる場合は、BFFが呼び出しを集約し、ペイロードを整形します。アナリティクス、同意管理、フィーチャーフラグ、コンテンツ管理の各サービスは薄いアダプターの背後に統合し、機能のコードに触れずにベンダーを置き換えられるようにします。

使用技術

ワークロードとお客様の既存環境に合わせて選定します。

  • TypeScript
  • React
  • Next.js
  • Vue
  • Nuxt
  • Vite
  • TanStack Query
  • Tailwind CSS
  • Storybook
  • Playwright
  • Vitest
  • axe-core
  • Workbox
  • OpenTelemetry

ゼロから本番へ

エンジニアリングライフサイクル

  1. 01

    アイデア

  2. 02

    ディスカバリー

  3. 03

    システムアーキテクチャ

  4. 04

    UX・プロダクトデザイン

  5. 05

    アプリケーション開発

  6. 06

    AI・データレイヤー

  7. 07

    クラウドインフラ

  8. 08

    セキュリティ

  9. 09

    QA・パフォーマンス

  10. 10

    デプロイ

  11. 11

    オブザーバビリティ

  12. 12

    継続的な進化

FAQ

よくあるご質問

フロントエンドアーキテクチャはユーザー体験にどう影響しますか

直接影響します。レンダリング戦略はコンテンツが表示されるまでの速さを、JavaScriptの量はページが応答するまでの速さを、状態とデータ取得の構成は操作が即座に感じられるかネットワークを待つことになるかを決めます。レイアウトの安定性、フォーカスの挙動、オフライン時の扱いも、ビジュアルデザインではなくアーキテクチャの帰結です。

Core Web Vitalsとは何ですか。なぜ重要なのですか

Googleが定義する3つのフィールド指標です。読み込みを表すLargest Contentful Paint、応答性を表すInteraction to Next Paint、視覚的安定性を表すCumulative Layout Shiftからなり、いずれも実際のページビューの75パーセンタイルで評価されます。実際の端末でユーザーが体験していることを表す指標であり、検索ランキングに用いられるシグナルの1つでもあるため重要です。

既存のフロントエンドを、全面的に書き直さずにモダナイズできますか

ほとんどの場合は可能です。段階的な移行では、新旧のアプリケーションを1つのルーターまたはリバースプロキシの背後に置き、セッションとデザイントークンを両者で共有しながら、ルートを1つずつ移していきます。これにより、移行の全期間を通じてプロダクトをリリース可能な状態に保ち、移行した各ルートを置き換え前のものと比較して計測できます。

プログレッシブWebアプリ(PWA)が適しているのはどのような場合ですか

インストール、オフラインアクセス、バックグラウンド同期の恩恵はあるものの、端末との深い統合までは必要としないプロダクトに適しています。PWAはWebサイトとコードベースを共有でき、ストア審査も不要ですが、一部の端末機能へのアクセス、バックグラウンド実行、プッシュ通知の挙動はプラットフォームによって異なり、特にiOSで違いが目立ちます。そうした制約が問題になる場合は、ネイティブまたはクロスプラットフォームのモバイルアプリケーションのほうが適しています。

アクセシビリティはどのように検証しますか

axe-coreなどの自動ツールで検出できるのは問題全体のうち少数にとどまり、主に名前やロールの欠落、コントラストの不足です。そのため、自動ツールはベースラインとしてCIで実行します。残りは、WCAG 2.2レベルAAの達成基準に照らして、重要なユーザージャーニーをキーボードのみで操作するウォークスルーと、NVDAおよびVoiceOverによるスクリーンリーダーテストで検証します。

本番環境で動くシステムが必要な方へ

構築しようとしているものをお聞かせください。エンジニアが内容を拝見し、2営業日以内にご返信します。