Vite + React でカスタムフィーチャーフラグを実装する

重岡 正 ·  Wed, February 4, 2026

フィーチャーフラグ(Feature Flags)は、コードをデプロイせずに機能の有効/無効を切り替える手法です。LaunchDarkly、Unleash、Flagsmith などのサードパーティサービスが有名ですが、シンプルな用途であれば自作も可能です。

今回、Vite 7 + React 19 + TanStack Router でカスタムフィーチャーフラグを実装するデモプロジェクトを作成しました。

フィーチャーフラグのメリット

メリット説明
継続的インテグレーション未完成の機能を安全に main ブランチへマージ
段階的リリース一部のユーザーにのみ機能を公開
A/B テスト異なる実装を実際のユーザーでテスト
即時ロールバック問題のある機能を再デプロイなしで無効化
トランクベース開発長期間のフィーチャーブランチを排除

トランクベース開発のフロー

従来の開発フローと、フィーチャーフラグを使ったトランクベース開発の比較です。

項目従来のフロートランクベース開発
ブランチ戦略長期間のフィーチャーブランチmain ブランチに直接マージ
PR サイズ大きな PR(数週間分の変更)小さな PR(こまめにマージ)
マージ頻度機能完成時に 1 回毎日〜数日ごと
リスクマージ時のコンフリクト大コンフリクト小、常にデプロイ可能
機能の公開マージ = リリースフラグ ON でリリース

フィーチャーフラグを使うことで、未完成の機能でも main ブランチにマージできます。フラグを OFF にしておけばユーザーには見えず、完成したらフラグを ON にするだけでリリースできます。

なぜカスタム実装なのか

観点カスタム実装サードパーティサービス
コスト無料$50-500+/月
複雑さシンプルSDK 統合が複雑
制御完全に自社管理ベンダー依存
機能基本的(拡張可能)高度(ターゲティング、分析)
レイテンシなし(クライアントサイド)ネットワークリクエスト必要
データ所在地自社インフラ日本リージョンがない場合も

シンプルな用途であればカスタム実装で十分です。また、企業によっては外部 SaaS の利用に制約があったり、データの所在地(日本リージョンの有無)が問題になるケースもあります。そうした制約がある場合、カスタム実装は有力な選択肢です。

高度なターゲティングや分析が必要な場合は、サードパーティサービスの検討をお勧めします。

技術スタック

今回のデモプロジェクトで使用した技術スタックです。

技術バージョン
Vite7.3.1
React19.2.4
TypeScript5.9.3
TanStack Router1.158.0
Turborepo2.8.3
Biome2.3.14
pnpm10.9.0
Node.js24.13.0

プロジェクト構造

pnpm workspaces と Turborepo を使ったモノレポ構成です。

apps/web - メインの React アプリ

ファイル説明
src/routes/__root.tsxルートレイアウト(FeatureFlagProvider を配置)
src/routes/index.tsxホームページ(デモ UI)
src/main.tsxエントリーポイント
vite.config.tsVite 設定

packages/feature-flags - フィーチャーフラグライブラリ

ファイル説明
src/index.tsパブリック API エクスポート
src/types.tsTypeScript 型定義
src/FeatureFlagContext.tsReact Context 定義
src/FeatureFlagProvider.tsxProvider コンポーネント
src/useFeatureFlag.tsカスタム React Hooks

ルート設定ファイル

ファイル説明
turbo.jsonTurborepo パイプライン設定
pnpm-workspace.yamlpnpm ワークスペース定義

2つのパターン

このプロジェクトでは、2つの異なるパターンでフィーチャーフラグを実装しています。

パターン解決タイミング用途
React Context(ランタイム)実行時A/B テスト、ユーザーターゲティング、動的トグル
環境変数(ビルドタイム)ビルド時環境別設定、CI/CD 統合

環境変数パターン

Vite の環境変数を使って、ビルド時にフラグを埋め込むパターンです。VITE_FF_ プレフィックスを使用します。

# .env
VITE_FF_EXPERIMENTAL_CHECKOUT=true
VITE_FF_ANALYTICS_DASHBOARD=true
VITE_FF_MAINTENANCE_MODE=false
import { isEnvFlagEnabled } from "@demo/feature-flags";
 
if (isEnvFlagEnabled("maintenanceMode")) {
  return <MaintenancePage />;
}

環境変数名は SCREAMING_SNAKE_CASE から camelCase に自動変換されます。

環境変数変換後のフラグ名
VITE_FF_NEW_DASHBOARDnewDashboard
VITE_FF_DARK_MODEdarkMode

パターンの使い分け

観点React Context環境変数
再ビルドなしで変更可能不可
ユーザー別ターゲティング可能不可
環境別設定複雑ネイティブサポート
バンドルサイズ最小限なし(デッドコード除去)

両方のパターンを組み合わせることも可能です。

import { useFeatureFlag, isEnvFlagEnabled } from "@demo/feature-flags";
 
function MyComponent() {
  // ビルドタイム: この環境で機能が利用可能か?
  const isFeatureAvailable = isEnvFlagEnabled("newFeature");
  // ランタイム: このユーザーは機能にロールアウトされているか?
  const isUserEnrolled = useFeatureFlag("newFeatureRollout");
 
  if (isFeatureAvailable && isUserEnrolled) {
    return <NewFeature />;
  }
  return <LegacyFeature />;
}

実装の詳細

フィーチャーフラグライブラリの構成

React の Context API を使った軽量な実装です。

型定義

// フィーチャーフラグはシンプルな key-value マップ
type FeatureFlags = Record<string, boolean>;
 
// Context で提供される値
type FeatureFlagContextValue = {
  flags: FeatureFlags;
  setFlag: (key: string, value: boolean) => void;
  isEnabled: (key: string) => boolean;
};

Provider コンポーネント

import { FeatureFlagProvider } from "@demo/feature-flags";
 
const defaultFlags = {
  newDashboard: true,    // デフォルトで有効
  darkMode: false,       // デフォルトで無効
  betaFeature: false,    // デフォルトで無効
};
 
function App() {
  return (
    <FeatureFlagProvider flags={defaultFlags}>
      <YourApp />
    </FeatureFlagProvider>
  );
}

カスタム Hooks

import { useFeatureFlag } from "@demo/feature-flags";
 
function Dashboard() {
  const isNewDashboardEnabled = useFeatureFlag("newDashboard");
 
  if (isNewDashboardEnabled) {
    return <NewDashboard />;
  }
  return <LegacyDashboard />;
}

よくあるパターン

パターン 1: コンポーネントの切り替え

function MyFeature() {
  const useNewImplementation = useFeatureFlag("newImplementation");
  return useNewImplementation ? <NewComponent /> : <OldComponent />;
}

パターン 2: 条件付きレンダリング

function Header() {
  const showBetaBadge = useFeatureFlag("betaFeature");
 
  return (
    <header>
      <h1>My App</h1>
      {showBetaBadge && <span className="badge">BETA</span>}
    </header>
  );
}

パターン 3: 動作の変更

function SubmitButton() {
  const useAsyncSubmit = useFeatureFlag("asyncSubmit");
 
  const handleClick = () => {
    if (useAsyncSubmit) {
      submitAsync();
    } else {
      submitSync();
    }
  };
 
  return <button onClick={handleClick}>Submit</button>;
}

セキュリティの考慮事項

クライアントサイドフィーチャーフラグの限界

この実装はクライアントサイドのフィーチャーフラグです。重要なセキュリティ上の注意点があります。

注意点説明
すべてのコードがバンドルに含まれるフラグが OFF でも、保護されたコンポーネントのコードは JavaScript バンドルに含まれます
フラグはバイパス可能ユーザーはブラウザの DevTools で React の state を変更し、任意のフラグを有効化できます
ソースコードは閲覧可能ソースマップやバンドル解析で「隠された」機能のコードが見えます

つまり、フラグが OFF でも /admin/beta のコンポーネント、フィーチャーフラグのロジックなど、ビルドされた JavaScript バンドル内のすべてのコードはユーザーから閲覧可能です。

保護レベルの比較

レベル方法防げる対象ユースケース
1. UI トグル(本デモ)条件付きレンダリングカジュアルユーザーUX 実験、段階的リリース
2. 遅延読み込みReact.lazy() + 動的 import軽度のコード検査やや良いが URL はアクセス可能
3. サーバーサイド認証API 認可チェック不正なデータアクセス機密データには必須
4. ビルド時除外環境変数でビルドコード露出環境別の別ビルド

推奨アーキテクチャ

レイヤー役割備考
クライアントサイドUI の表示制御のみ(UX 目的)セキュリティ用途には使用不可。クライアントのコードはすべて公開情報と考える
サーバーサイド認証・認可・データフィルタリングユーザーの身元確認、リクエストごとの権限チェック、フラグのサーバーサイド評価、許可されたデータのみ返す

クライアントサイドフラグが適切な場面

  • A/B テスト(UI バリエーション)
  • UI 変更の段階的リリース
  • ベータ機能プレビュー(非機密)
  • ダークモード、テーマ切り替え
  • UI レイアウト実験

サーバーサイド保護が必要な場面

  • 機密データを扱う管理画面
  • プレミアム/有料機能
  • ユーザーデータのアクセス制御
  • セキュリティに関わるすべての機能

本番環境での考慮事項

このデモは意図的にシンプルに作られています。本番環境では以下を検討してください。

フラグの永続化

const [flags, setFlags] = useState(() => {
  const saved = localStorage.getItem("featureFlags");
  return saved ? JSON.parse(saved) : defaultFlags;
});
 
useEffect(() => {
  localStorage.setItem("featureFlags", JSON.stringify(flags));
}, [flags]);

リモートからのフラグ取得

useEffect(() => {
  fetch("/api/feature-flags")
    .then((res) => res.json())
    .then((remoteFlags) => setFlags(remoteFlags));
}, []);

ユーザーベースのターゲティング

const flags = {
  betaFeature: user.isBetaTester,
  adminPanel: user.role === "admin",
};

環境ベースのフラグ

const flags = {
  debugMode: import.meta.env.DEV,
  newFeature: import.meta.env.VITE_ENABLE_NEW_FEATURE === "true",
};

まとめ

React Context を使ったシンプルなフィーチャーフラグシステムを実装しました。

  • 軽量: 外部依存なし、React のみ
  • 型安全: TypeScript で完全に型付け
  • トランクベース開発: 未完成機能を安全にマージ可能

ただし、クライアントサイドフィーチャーフラグはセキュリティ目的には使えません。機密性の高い機能には、必ずサーバーサイドでの認証・認可を実装してください。

デモプロジェクトを clone して試してみてください。

git clone https://github.com/codenote-net/vite-feature-flag-demo.git
cd vite-feature-flag-demo
pnpm install
pnpm dev

以上、Vite + React でカスタムフィーチャーフラグを実装してトランクベース開発を試してみた、現場からお送りしました。

参考情報