Skip to main content

Feature Flags

Within the utils package, we expose a set of utility functions that allow consumers to use flags to enable or disable features within their application with minimal configuration. The utils rely on the official @vercel/flags package (only stable parts of it), extending its functionality to cover:

  • Integration with the Vercel toolbar (enabling easy overrides of flag values on preview deployments)
  • Integration with the edge config store to switch flags on and off without re-deployment
  • Encrypted flag values by default (no leakage in the DOM), but comes with an option to be disabled to make flags available on the client (see explanation below)
  • A utility to fetch flag values within getServerSideProps and middleware.ts
  • A utility to fetch flag values on the client (unencrypted flags only!)
  • Optionally 100% type-safe flags with the help of a script that fetches the flags from the edge config store and generates types based on them

Why can't I use encrypted flags on the client?

The package uses a private environment variable to encrypt and decrypt the cookies used to store flag values and overrides. Since this variable is not available on the client, flags will have to be stored in clear text. If you need to use your flags on the client and this is an acceptable tradeoff for you, you can set encryptionMode to false on every function this packages exposes.
Note: If you disable encryption somewhere along the chain, you'll need to disable it everywhere, otherwise you will encounter errors.

Setup

Preparation

  1. Create a new edge config store with an object flags in it. Each flag must contain a defaultValue and an options array. The other fields are optional.
{
"flags": {
"exampleFlag": {
"description": "I am a boolean example",
"origin": "/accounts",
"defaultValue": true,
"options": [
{
"value": true
},
{
"value": false
}
]
},
"anotherFlag": {
"description": "I am a numerical example",
"origin": "/accounts",
"defaultValue": 1,
"options": [
{
"value": 1
},
{
"value": 2
}
]
},
"thirdFlag": {
"description": "I am a string example",
"origin": "/accounts",
"defaultValue": "test1",
"options": [
{
"value": "test1"
},
{
"value": "test2"
}
]
}
}
}
  1. Make sure you do have the EDGE_CONFIG environment variable that was generated by Vercel available in your local environment.
  2. Generate the flags secret and store it in an environment variable FLAGS_SECRET (this is used by the package to encrypt your flags cookies)
node -e "console.log(crypto.randomBytes(32).toString('base64url'))"

Integration with your codebase

You are now ready to consume flags wherever you need them.

0. (Optional) Generate types

You can use the provided script to generate a d.ts file containing your flag values. It declares an interface FeatureFlagsType. The utility functions getFlags and getFlagsOnClient (see usage below) accept a generic object to type them like so: getFlags<FeatureFlagsType>({...})

Install the necessary script runner as dev dependency

pnpm install tsx -D

package.json:

    "scripts": {
...
// Make sure to specify the --dir param!
"generate-featureflags-declaration-file": "generate-featureflags-declaration-file --dir='src/types'"
},

In order to consume your flags, you need to make them available to your application via a cookie. Therefore, the package exposes a middleware util for you to wrap you res in like so:

import { setFeatureFlagCookie } from '@krakentech/blueprint-utils/feature-flags';

export async function middleware(req: NextRequest) {
const res = NextResponse;

//...you might do some more stuff to some requests here

return setFeatureFlagCookie({
res,
encryptionMode: false, // is true by default, ONLY disable of you need to consume the flags on the client
});
}

If you don't want to wrap all of your requests with the flags cookie, make sure you wrap at least those, which you need to consume from. Example: If you want to consume the flags on /dashboard/subpage, make sure you wrap the request for it in the middleware

2. a) Usage on the Server

getServerSideProps
import { getFlags } from '@krakentech/blueprint-utils/feature-flags';
const Page = ({ flags }: { flags?: FeatureFlagsType }) => {
return (
<Stack gap="lg" direction="vertical">
{flags?.exampleFlag && (
<div>
OMG FEATURE FLAGS!!!111 🤯🤯🤯 (Set exampleFlag to false to
hide me)
</div>
)}
{flags?.anotherFlag === 1 && (
<div>ANOTHER FLAG HERE (Set anotherFlag to 2 to hide me)</div>
)}
<div></div>
</Stack>
);
};

export const getServerSideProps: GetServerSideProps = async (context) => {
const flags = await getFlags<FeatureFlagsType>({
req: context.req,
encryptionMode: true, // is true by default, ONLY disable of you need to consume the flags on the client
});
return {
props: {
flags: flags,
},
};
};
middleware.ts
import { getFlags } from '@krakentech/blueprint-utils/feature-flags';

export async function middleware(req: NextRequest) {
const flags = getFlags<FeatureFlagsType>({
req,
encryptionMode: true, // is true by default, ONLY disable of you need to consume the flags on the client
});

// consume a flag value and do some re-directing for example
}

3. b) Usage on the Client

import { getFlagsOnClient } from '@krakentech/blueprint-utils/feature-flags';

const Component = ({ flags }: { flags?: FeatureFlagsType }) => {
// be warned, this will throw an error if you are using encrypted flags
const clientSideFlags = getFlagsOnClient<FeatureFlagsType>();

return (
<Stack gap="lg" direction="vertical">
{clientSideFlags?.exampleFlag && (
<div>
OMG FEATURE FLAGS!!!111 🤯🤯🤯 (Set exampleFlag to false to
hide me)
</div>
)}
{clientSideFlags?.anotherFlag === 1 && (
<div>ANOTHER FLAG HERE (Set anotherFlag to 2 to hide me)</div>
)}
<div></div>
</Stack>
);
};

Troubleshooting Errors

  • You can't use encrypted cookies on the client -> You're trying to use getFlagsOnClient while having encryptionMode set to true somewhere.
  • Set encryptionMode to true to use encrypted cookies -> You trying to use getFlags with encryption turned off while having it set to true in your middleware setFeatureFlagCookie function.

Note: If you're switching between encrypted and unencrypted flag values during development, you might need to clear out your cookies to avoid these errors.

Integration with the Vercel Toolbar

Currently, you can only change the values of your feature flag directly in the edge config, which kind of defeats the purpose at least for development reasons. What you want is a way to conveniently override flag values but only for you, on your machine. To achieve this, you can install the Vercel toolbar package.

pnpm i @vercel/toolbar

The following steps are very close to the official tutorial, so feel free to reference it for more context.

1. Add the api route

The toolbar interfaces with your flags via an api route, which has to be located here: /pages/api/vercel/flags.ts

import { featureFlagsVercelToolbarHandler } from '@krakentech/blueprint-utils/feature-flags';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
request: NextApiRequest,
response: NextApiResponse
) {
await featureFlagsVercelToolbarHandler({
req: request,
res: response,
encryptionMode: true, // is true by default, ONLY disable of you need to consume the flags on the client
});
}

2. Wrap your next.config

To be compatible with the pages router, the toolbar needs a rewrite, since we cant add a route directly to /.well-known This used to be described in the docs, but has since been removed.

next.config.js
const withVercelToolbar = require('@vercel/toolbar/plugins/next')();

/** @type {import('next').NextConfig} */
const moduleExports = {
// ...your config
async rewrites() {
return [
{
source: '/.well-known/vercel/flags',
destination: '/api/vercel/flags',
},
// ...more of your rewrites
];
},
};

module.exports = withVercelToolbar(moduleExports);
next.config.mjs
import { withVercelToolbar } from '@vercel/toolbar/plugins/next';


const nextConfig = {
// ...your config

async rewrites() {
return [
{
source: '/.well-known/vercel/flags',
destination: '/api/vercel/flags',
},
// ...more of your rewrites
];
},
};

export default withVercelToolbar()(
nextConfig, {
...
});

3. Add the toolbar to your local environment

_app.ts

import { VercelToolbar } from '@vercel/toolbar/next';
import type { AppProps } from 'next/app';

export default function MyApp({ Component, pageProps }: AppProps) {
const shouldInjectToolbar = process.env.NODE_ENV === 'development';
return (
<>
<Component {...pageProps} />
{shouldInjectToolbar && <VercelToolbar />}
</>
);
}

Make sure you also enable the toolbar for preview deployments in your Vercel settings!

4. Expose the current flag values to the toolbar on a given pageProps

The FlagValues component will emit a script tag with a data-flag-values attribute, which get picked up by Vercel Toolbar. Vercel Toolbar then combines the flag values with the definitions returned by your API endpoint. This is technically optional, but without this component, the toolbar won't show you in which state the flags currently are.

import { getFlags } from '@krakentech/blueprint-utils/feature-flags';
import { FlagValues } from '@vercel/flags/react';

const Page = ({ flags, encryptedFlagValues }: { flags?: FeatureFlagsType, encryptedFlagValues: string }) => {
return (
{flags && <FlagValues values={encryptedFlagValues} />}
<Stack gap="lg" direction="vertical">
{flags?.exampleFlag && (
<div>
OMG FEATURE FLAGS!!!111 🤯🤯🤯 (Set exampleFlag to false to
hide me)
</div>
)}
{flags?.anotherFlag === 1 && (
<div>ANOTHER FLAG HERE (Set anotherFlag to 2 to hide me)</div>
)}
<div></div>
</Stack>
);
};

export const getServerSideProps: GetServerSideProps = async (context) => {
const flags = await getFlags<FeatureFlagsType>({
req: context.req,
encryptionMode: true, // is true by default, ONLY disable of you need to consume the flags on the client
});

// if you're not using encryption, you can simply pass the flags directly to <FlagValues/>
const encryptedFlagValues = await encrypt<FlagValuesType>(flags);
return {
props: {
flags: flags,
encryptedFlagValues: encryptedFlagValues
},
};
};

That's it! You're good to go!