flags-sdk

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flags SDK

Flags SDK

The Flags SDK (
flags
npm package) is a feature flags toolkit for Next.js and SvelteKit. It turns each feature flag into a callable function, works with any flag provider via adapters, and keeps pages static using the precompute pattern.
Flags SDK(
flags
npm包)是面向Next.js和SvelteKit的功能标志工具集。它将每个功能标志转化为可调用的函数,通过适配器支持任意标志提供方,并使用预计算模式保持页面静态化。

Core Concepts

核心概念

Flags as Code

标志即代码

Each flag is declared as a function — no string keys at call sites:
ts
import { flag } from 'flags/next';

export const exampleFlag = flag({
  key: 'example-flag',
  decide() { return false; },
});

// Usage: just call the function
const value = await exampleFlag();
每个标志都声明为函数,调用处无需使用字符串key:
ts
import { flag } from 'flags/next';

export const exampleFlag = flag({
  key: 'example-flag',
  decide() { return false; },
});

// Usage: just call the function
const value = await exampleFlag();

Server-Side Evaluation

服务端评估

Evaluate flags server-side to avoid layout shift, keep pages static, and maintain confidentiality. Use routing middleware + precompute to serve static variants from CDN.
在服务端评估标志可以避免布局偏移、保持页面静态化、保障数据保密性。使用路由中间件+预计算从CDN返回静态变体页面。

Adapter Pattern

适配器模式

Adapters replace
decide
and
origin
on a flag declaration, enabling provider-agnostic flags:
ts
import { flag } from 'flags/next';
import { statsigAdapter } from '@flags-sdk/statsig';

export const myGate = flag({
  key: 'my_gate',
  adapter: statsigAdapter.featureGate((gate) => gate.value),
  identify,
});
适配器会替换标志声明中的
decide
origin
属性,实现与提供方无关的标志定义:
ts
import { flag } from 'flags/next';
import { statsigAdapter } from '@flags-sdk/statsig';

export const myGate = flag({
  key: 'my_gate',
  adapter: statsigAdapter.featureGate((gate) => gate.value),
  identify,
});

Declaring Flags

声明标志

Basic Flag

基础标志

ts
import { flag } from 'flags/next'; // or 'flags/sveltekit'

export const showBanner = flag<boolean>({
  key: 'show-banner',
  description: 'Show promotional banner',
  defaultValue: false,
  options: [
    { value: false, label: 'Hide' },
    { value: true, label: 'Show' },
  ],
  decide() { return false; },
});
ts
import { flag } from 'flags/next'; // or 'flags/sveltekit'

export const showBanner = flag<boolean>({
  key: 'show-banner',
  description: 'Show promotional banner',
  defaultValue: false,
  options: [
    { value: false, label: 'Hide' },
    { value: true, label: 'Show' },
  ],
  decide() { return false; },
});

Flag with Evaluation Context

带评估上下文的标志

Use
identify
to establish who the request is for;
decide
receives the entities:
ts
import { dedupe, flag } from 'flags/next';
import type { ReadonlyRequestCookies } from 'flags';

interface Entities {
  user?: { id: string };
}

const identify = dedupe(
  ({ cookies }: { cookies: ReadonlyRequestCookies }): Entities => {
    const userId = cookies.get('user-id')?.value;
    return { user: userId ? { id: userId } : undefined };
  },
);

export const dashboardFlag = flag<boolean, Entities>({
  key: 'new-dashboard',
  identify,
  decide({ entities }) {
    if (!entities?.user) return false;
    return ['user1', 'user2'].includes(entities.user.id);
  },
});
使用
identify
定义请求对应的主体,
decide
方法可以接收该实体信息:
ts
import { dedupe, flag } from 'flags/next';
import type { ReadonlyRequestCookies } from 'flags';

interface Entities {
  user?: { id: string };
}

const identify = dedupe(
  ({ cookies }: { cookies: ReadonlyRequestCookies }): Entities => {
    const userId = cookies.get('user-id')?.value;
    return { user: userId ? { id: userId } : undefined };
  },
);

export const dashboardFlag = flag<boolean, Entities>({
  key: 'new-dashboard',
  identify,
  decide({ entities }) {
    if (!entities?.user) return false;
    return ['user1', 'user2'].includes(entities.user.id);
  },
});

Flag with Adapter

带适配器的标志

ts
import { flag } from 'flags/next';
import { vercelAdapter } from '@flags-sdk/vercel';

export const exampleFlag = flag({
  key: 'example-flag',
  adapter: vercelAdapter(),
});
ts
import { flag } from 'flags/next';
import { vercelAdapter } from '@flags-sdk/vercel';

export const exampleFlag = flag({
  key: 'example-flag',
  adapter: vercelAdapter(),
});

Key Parameters

核心参数

ParameterTypeDescription
key
string
Unique flag identifier
decide
function
Resolves the flag value
defaultValue
any
Fallback if
decide
returns undefined or throws
description
string
Shown in Flags Explorer
origin
string
URL to manage the flag in provider dashboard
options
{ label?: string, value: any }[]
Possible values, used for precompute + Flags Explorer
adapter
Adapter
Provider adapter implementing
decide
and
origin
identify
function
Returns evaluation context (entities) for
decide
参数名类型描述
key
string
唯一的标志标识符
decide
function
解析返回标志值
defaultValue
any
decide
返回undefined或抛出异常时的回退值
description
string
在Flags Explorer中展示的描述信息
origin
string
提供方控制台中管理该标志的URL
options
{ label?: string, value: any }[]
可选值列表,用于预计算和Flags Explorer展示
adapter
Adapter
实现了
decide
origin
的提供方适配器
identify
function
返回供
decide
使用的评估上下文(实体信息)

Dedupe

Dedupe

Wrap shared functions (especially
identify
) in
dedupe
to run them once per request:
ts
import { dedupe } from 'flags/next';

const identify = dedupe(({ cookies }) => {
  return { user: { id: cookies.get('uid')?.value } };
});
Note:
dedupe
is not available in Pages Router.
将共享函数(尤其是
identify
)用
dedupe
包裹,使其每次请求仅执行一次:
ts
import { dedupe } from 'flags/next';

const identify = dedupe(({ cookies }) => {
  return { user: { id: cookies.get('uid')?.value } };
});
注意:
dedupe
在Pages Router中不可用。

Flags Explorer Setup

Flags Explorer 配置

Next.js (App Router)

Next.js (App Router)

ts
// app/.well-known/vercel/flags/route.ts
import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next';
import * as flags from '../../../../flags';

export const GET = createFlagsDiscoveryEndpoint(async () => {
  return getProviderData(flags);
});
ts
// app/.well-known/vercel/flags/route.ts
import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next';
import * as flags from '../../../../flags';

export const GET = createFlagsDiscoveryEndpoint(async () => {
  return getProviderData(flags);
});

With External Provider Data

对接外部提供方数据

ts
import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next';
import { getProviderData as getStatsigProviderData } from '@flags-sdk/statsig';
import { mergeProviderData } from 'flags';
import * as flags from '../../../../flags';

export const GET = createFlagsDiscoveryEndpoint(async () => {
  return mergeProviderData([
    getProviderData(flags),
    getStatsigProviderData({
      consoleApiKey: process.env.STATSIG_CONSOLE_API_KEY,
      projectId: process.env.STATSIG_PROJECT_ID,
    }),
  ]);
});
ts
import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next';
import { getProviderData as getStatsigProviderData } from '@flags-sdk/statsig';
import { mergeProviderData } from 'flags';
import * as flags from '../../../../flags';

export const GET = createFlagsDiscoveryEndpoint(async () => {
  return mergeProviderData([
    getProviderData(flags),
    getStatsigProviderData({
      consoleApiKey: process.env.STATSIG_CONSOLE_API_KEY,
      projectId: process.env.STATSIG_PROJECT_ID,
    }),
  ]);
});

SvelteKit

SvelteKit

ts
// src/hooks.server.ts
import { createHandle } from 'flags/sveltekit';
import { FLAGS_SECRET } from '$env/static/private';
import * as flags from '$lib/flags';

export const handle = createHandle({ secret: FLAGS_SECRET, flags });
ts
// src/hooks.server.ts
import { createHandle } from 'flags/sveltekit';
import { FLAGS_SECRET } from '$env/static/private';
import * as flags from '$lib/flags';

export const handle = createHandle({ secret: FLAGS_SECRET, flags });

FLAGS_SECRET

FLAGS_SECRET

Required for precompute and Flags Explorer. Must be 32 random bytes, base64-encoded:
sh
node -e "console.log(crypto.randomBytes(32).toString('base64url'))"
Store as
FLAGS_SECRET
env var. On Vercel:
vc env add FLAGS_SECRET
then
vc env pull
.
预计算和Flags Explorer必需的配置项。必须是32随机字节的base64编码值:
sh
node -e "console.log(crypto.randomBytes(32).toString('base64url'))"
存储为
FLAGS_SECRET
环境变量。在Vercel上执行:
vc env add FLAGS_SECRET
,然后执行
vc env pull

Precompute Pattern (Overview)

预计算模式(概述)

Use precompute to keep pages static while using feature flags. Middleware evaluates flags and encodes results into the URL via rewrite. The page reads precomputed values instead of re-evaluating.
High-level flow:
  1. Declare flags and group them in an array
  2. Call
    precompute(flagGroup)
    in middleware, get a
    code
    string
  3. Rewrite request to
    /${code}/original-path
  4. Page reads flag values from
    code
    :
    await myFlag(code, flagGroup)
For full implementation details, see framework-specific references:
  • Next.js: See references/nextjs.md — covers proxy middleware, precompute setup, ISR, generatePermutations, multiple groups
  • SvelteKit: See references/sveltekit.md — covers reroute hook, middleware, precompute setup, ISR, prerendering
使用预计算可以在使用功能标志的同时保持页面静态化。中间件评估标志并通过重写将结果编码到URL中。页面直接读取预计算值,无需重新评估。
高层流程:
  1. 声明标志并将其分组到数组中
  2. 在中间件中调用
    precompute(flagGroup)
    ,获取
    code
    字符串
  3. 将请求重写到
    /${code}/original-path
  4. 页面从
    code
    中读取标志值:
    await myFlag(code, flagGroup)
完整实现细节请参考对应框架的文档:
  • Next.js: 查看 references/nextjs.md — 包含代理中间件、预计算配置、ISR、generatePermutations、多分组等内容
  • SvelteKit: 查看 references/sveltekit.md — 包含 reroute 钩子、中间件、预计算配置、ISR、预渲染等内容

Custom Adapters

自定义适配器

Create an adapter factory returning an object with
origin
and
decide
:
ts
import type { Adapter } from 'flags';

export function createMyAdapter(/* options */) {
  return function myAdapter<ValueType, EntitiesType>(): Adapter<ValueType, EntitiesType> {
    return {
      origin(key) {
        return `https://my-provider.com/flags/${key}`;
      },
      async decide({ key }): Promise<ValueType> {
        // evaluate against your provider
        return false as ValueType;
      },
    };
  };
}
创建适配器工厂函数,返回包含
origin
decide
属性的对象:
ts
import type { Adapter } from 'flags';

export function createMyAdapter(/* options */) {
  return function myAdapter<ValueType, EntitiesType>(): Adapter<ValueType, EntitiesType> {
    return {
      origin(key) {
        return `https://my-provider.com/flags/${key}`;
      },
      async decide({ key }): Promise<ValueType> {
        // evaluate against your provider
        return false as ValueType;
      },
    };
  };
}

Encryption Functions

加密函数

For keeping flag data confidential in the browser (used by Flags Explorer):
FunctionPurpose
encryptFlagValues
Encrypt resolved flag values
decryptFlagValues
Decrypt flag values
encryptFlagDefinitions
Encrypt flag definitions/metadata
decryptFlagDefinitions
Decrypt flag definitions
encryptOverrides
Encrypt toolbar overrides
decryptOverrides
Decrypt toolbar overrides
All use
FLAGS_SECRET
by default. Example:
tsx
import { encryptFlagValues } from 'flags';
import { FlagValues } from 'flags/react';

async function ConfidentialFlags({ values }) {
  const encrypted = await encryptFlagValues(values);
  return <FlagValues values={encrypted} />;
}
用于在浏览器中保障标志数据的保密性(供Flags Explorer使用):
函数名用途
encryptFlagValues
加密解析后的标志值
decryptFlagValues
解密标志值
encryptFlagDefinitions
加密标志定义/元数据
decryptFlagDefinitions
解密标志定义
encryptOverrides
加密工具栏覆盖配置
decryptOverrides
解密工具栏覆盖配置
所有函数默认使用
FLAGS_SECRET
。示例:
tsx
import { encryptFlagValues } from 'flags';
import { FlagValues } from 'flags/react';

async function ConfidentialFlags({ values }) {
  const encrypted = await encryptFlagValues(values);
  return <FlagValues values={encrypted} />;
}

React Components

React 组件

tsx
import { FlagValues, FlagDefinitions } from 'flags/react';

// Renders script tag with flag values for Flags Explorer
<FlagValues values={{ myFlag: true }} />

// Renders script tag with flag definitions for Flags Explorer
<FlagDefinitions definitions={{ myFlag: { options: [...], description: '...' } }} />
tsx
import { FlagValues, FlagDefinitions } from 'flags/react';

// 渲染包含标志值的script标签,供Flags Explorer使用
<FlagValues values={{ myFlag: true }} />

// 渲染包含标志定义的script标签,供Flags Explorer使用
<FlagDefinitions definitions={{ myFlag: { options: [...], description: '...' } }} />

References

参考资料

Detailed framework and provider guides are in separate files to keep context lean:
  • references/nextjs.md: Next.js quickstart, App Router, Pages Router, middleware/proxy, precompute, dedupe, dashboard pages, marketing pages, suspense fallbacks
  • references/sveltekit.md: SvelteKit quickstart, hooks setup, toolbar, precompute with reroute + middleware, dashboard pages, marketing pages
  • references/providers.md: All provider adapters — Vercel, Edge Config, Statsig, LaunchDarkly, PostHog, GrowthBook, Hypertune, Flagsmith, Reflag, Split, Optimizely, OpenFeature, and custom adapters
  • references/api.md: Full API reference for
    flags
    ,
    flags/react
    ,
    flags/next
    , and
    flags/sveltekit
为了保持内容精简,详细的框架和提供方指南放在单独文件中:
  • references/nextjs.md:Next.js快速入门、App Router、Pages Router、中间件/代理、预计算、dedupe、仪表盘页面、营销页面、suspense 降级方案
  • references/sveltekit.md:SvelteKit快速入门、hooks配置、工具栏、基于reroute+中间件的预计算、仪表盘页面、营销页面
  • references/providers.md:所有提供方适配器说明 — Vercel、Edge Config、Statsig、LaunchDarkly、PostHog、GrowthBook、Hypertune、Flagsmith、Reflag、Split、Optimizely、OpenFeature以及自定义适配器
  • references/api.md
    flags
    flags/react
    flags/next
    flags/sveltekit
    的完整API参考