【Next.js】フィーチャーフラグの導入方法

2024/04/23に公開

フィーチャーフラグとは

コードを書き換えることなく、特定の機能を動的に有効化したり無効化することができる開発手法です。
この手法を用いることで、すべてのコードが本番環境にデプロイされている状態で、開発中またはテスト中の機能をユーザーには見えないように管理することができます。

また、フィーチャートグル(Feature Toggle)と表現されることもありますが、本記事ではフィーチャーフラグ(Feature Flag)という表現を用いています。

フィーチャートグル(Wikipedia)

メリット

ブランチワークの複雑性の解消

フィーチャーフラグを使って開発中の機能をOFFの状態で保持することで、未完成の機能を安心してメインブランチにマージすることが可能になります。
これにより、特定の機能を集約したepicブランチなどの運用が不要になるため、開発プロセスが効率化され、チームの生産性が向上します。

ビックバンリリースを回避できる

各機能を個別にオンにすることにより、全ての新機能を一度に導入する大規模リリース(ビックバンリリース)のリスクを減らすことができます。
そのため、リリース時のリスクやユーザーへの影響を最小限に抑えることができます。
また、不具合が発生した場合でも、一度のリリースでの変更が小さいため、原因の特定やロールバックを迅速に行うことができます。

全体リリース前に本番環境でのテストが可能になる

全体リリースを行う前に、特定のユーザーグループのみに機能を公開するなどして、本番環境で個々の機能をテストすることが可能になります。
これにより、開発者は実際の運用環境での動作確認ができ、バグの早期発見と修正が可能となり、最終的な製品の品質が向上します。

デメリット

コード量が増える

一時的ではあるものの、単純にコード量が増えるということが挙げられます。
これは、新旧の機能が同時に存在するために起こり得る状況です。
そのため、コード内に複数の開発中の機能が共存することで、可読性や保守性が低下する恐れがあります。

フラグの管理コストがかかる

フィーチャーフラグを多用すると、それぞれのフラグを適切に管理し続ける必要があります。
そのため、フラグの数が増えるほど、設定や追跡が煩雑になります。
特に大規模なシステムでは、フラグが適切に管理されていない場合に、設定ミスやオーバーライドが原因で予期せぬバグが発生する可能性があります。

実装

Next.jsでフィーチャーフラグを効果的に管理するための設定方法を紹介します。

環境変数の設定

フィーチャーフラグを管理する際、環境変数を活用します。
複数のフラグが存在する場合は、カンマ区切りでフラグを列挙します。
各フラグには、機能名やプロジェクト名を用いることで、どのフィーチャーが何を指しているかが一目で理解しやすくなります。

例では、以下のように設定しています。

  • newApp: 新しく追加する機能を有効化するフラグ
  • renewalApp: 既存の機能をアップデートして新旧の入れ替えを行うフラグ
.env.local
FEATURES=newApp,renewalApp

初期状態の定義

各フィーチャーの初期状態を定義します。
先ほどFEATURES環境変数に設定したフラグ名をキーとして使用し、全てのフラグの初期値をfalseに設定しています。

features.js
module.exports = {
    newApp: false,
    renewalApp: false,
}

Next.jsの設定ファイルの調整

Next.jsの設定ファイルにおいて、envファイルからフィーチャーフラグを読み込み、WebpackDefinePluginを使用して、FEATURES変数をグローバル変数として利用できるようにしています。
これにより、アプリケーション全体で環境変数から渡されたフィーチャーフラグの値を容易に参照できるようになります。

next.config.mjs
import features from './features.js'

/** @type {import('next').NextConfig} */
const nextConfig = {
    webpack: (config, { webpack }) => {
        // splittedFeatures: ['newApp', 'renewalApp']
        const splittedFeatures = process.env.FEATURES
            ? process.env.FEATURES.split(',')
            : []
        // featureToggles: { newApp: boolean, renewalApp: boolean }
        const featureToggles = Object.keys(features).reduce((acc, key) => {
            acc[key] = splittedFeatures.includes(key)
            return acc
        }, {})
        config.plugins.push(
            new webpack.DefinePlugin({
                // { FEATURES: { newApp: boolean, renewalApp: boolean } }
                FEATURES: featureToggles,
            })
        )

        return config
    },
}

export default nextConfig

TypeScriptの宣言ファイル

FEATURES変数をアプリケーション全体で問題なく使用可能にするために、TypeScriptの型システムでも認識できるようにグローバル変数の宣言を追加します。
これにより、TypeScriptプロジェクトでの型エラーを回避できます。

types/global.d.ts
import type features from '../features'

declare global {
    const FEATURES: typeof features
}

使用例

実際にフィーチャーフラグをどのように利用するかの具体例を紹介します。

コンポーネントの表示を切り替える

FEATURESフラグの状態に応じて、特定のコンポーネントを表示したり、新旧のコンポーネントを出し分けたりします。これにより、機能のテストや段階的なリリースが容易になります。

{FEATURES.newApp && (
  <NewAppComponent/>  // FEATURES.newApp が true の場合に表示される
)}
{FEATURES.renewalApp ? (
  <RenewalAppComponent/>  // FEATURES.renewalApp が true の場合に表示される
) : (
  <OldAppComponent/>  // FEATURES.renewalApp が false の場合に表示される
)}

実行する処理を切り替える

フラグの状態に応じて、異なる関数を実行します。この方法で、特定の機能のテストや条件に基づいた動作の変更を行うことができます。

const onClick = () => {
    if(FEATURES.renewalApp) {
        renewalAppFunc()  // FEATURES.renewalApp が true の場合に実行される
    } else {
        oldAppFunc()  // FEATURES.renewalApp が false の場合に実行される
    }
}

特定のユーザーのみに機能を公開する

フィーチャーフラグと他の条件を組み合わせることで、特定のユーザーにのみ機能を公開することも可能です。

// 機能を公開してもいいユーザーかどうかをチェック
const enabledUser = enabledUserIds.includes(user.id)

{FEATURES.newApp && enabledUser (
  <NewAppComponent/>
)}

このとき、「なぜ enabledUser だけではなく FEATURES.newApp も必要なのか?」と疑問に思うかもしれません。
ですが、FEATURES.newAppのような静的なフラグを設定しておくことで、フィーチャーフラグがfalsyの値を持つ場合、ビルド時にTree shakingの対象となり、実行されないコードとして削除されます。
そのため、バンドルサイズの削減によるパフォーマンスの向上や、公開前の機能のコードが本番環境にデプロイされることを防ぐことができます。

特定の環境のみに開発中の機能を公開する

フィーチャーフラグを利用して特定の環境にのみ開発中の機能を公開する方法を紹介します。
このアプローチによって、開発環境やステージング環境で新機能をテストしつつ、本番環境ではそれらを非表示に保つことを可能にします。

以下の例では、開発環境用の.env.developmentファイルのみにFEATURES環境変数が設定されています。
これにより、FEATURES.newAppFEATURES.renewalAppを含む機能が開発環境でのみ有効化されます。
ステージングと本番の環境変数は空に設定されており、これらの環境ではデフォルトで無効になります。

.env.development
FEATURES=newApp,renewalApp
.env.staging
FEATURES=
.env.production
FEATURES=

この方法により、ソースコードは全ての環境にデプロイされても、特定の環境でのみ機能が公開・利用可能となります。

まとめ

この記事では、Next.js を使用してフィーチャーフラグを導入し管理する方法について解説しました。
フィーチャーフラグを用いることで、開発プロセスが柔軟になり、新機能のテストやリリースが容易になるため、予期せぬエラーやユーザーへの影響を最小限に抑えることができます。

フィーチャーフラグは特に、アジャイル開発のような短いサイクルでリリースを重ねるチームにとって有効です。
効率的な開発フローを保ちつつ、ユーザーの実際の反応に基づいて製品を調整することが、この手法の大きな利点です。

これらの利点を活用して、柔軟で効率的な開発フローを維持しながら、ユーザーに価値ある製品を持続的に提供していきましょう。

Discussion