
Security News
TypeScript is Porting Its Compiler to Go for 10x Faster Builds
TypeScript is porting its compiler to Go, delivering 10x faster builds, lower memory usage, and improved editor performance for a smoother developer experience.
@happykit/flags
Advanced tools
Add Feature Flags to your Next.js application with a single React Hook. This package integrates your Next.js application with HappyKit Flags. Create a free happykit.dev account to get started.
Key Features
useFlags()
hooknpm install @happykit/flags
Configure your application in _app.js
.
// _app.js
import { configure } from "@happykit/flags/config";
configure({ envKey: process.env.NEXT_PUBLIC_FLAGS_ENVIRONMENT_KEY });
If you don't have a custom _app.js
yet, see the Custom App
section of the Next.js docs for setup instructions.
Create an account on happykit.dev
to receive your envKey
. You'll find it in the Keys section of your project settings once you created a project.
Make sure the environment variable containing the envKey
starts with NEXT_PUBLIC_
so the value is available on the client side.
Store your envKey
in .env.local
:
# .env.local
NEXT_PUBLIC_FLAGS_ENVIRONMENT_KEY=flags_pub_development_xxxxxxxxxx
Later on, don't forget to also provide the environment variable in production.
There's also a full walkthrough of the setup, which explains the setup in your project and in HappyKit Flags itself.
You can load flags on the client with a single useFlags
call.
// pages/foo.js
import { useFlags } from "@happykit/flags/client";
export default function FooPage(props) {
const { flags } = useFlags();
return flags.xzibit ? 'Yo dawg' : 'Hello';
}
Or with server-side rendering
// pages/foo.js
import { useFlags } from "@happykit/flags/client";
import { getFlags } from "@happykit/flags/server";
export const getServerSideProps = async (context) => {
const { initialFlagState } = await getFlags({ context });
return { props: { initialFlagState } };
};
export default function FooPage(props) {
const { flags } = useFlags({ initialState: props.initialFlagState });
return flags.xzibit ? 'Yo dawg' : 'Hello';
}
configure
configure(options)
options.envKey
(string) required: Your HappyKit Flags Client Idoptions.defaultFlags
(object) optional: Key-value pairs of flags and their values. These values are used as fallbacks in useFlags
and getFlags
. The fallbacks are used while the actual flags are loaded, in case a flag is missing or when the request loading the flags fails for unexpected reasons. If you don't declare defaultFlags
, then the flag values will be undefined
.options.disableCache
(boolean) optional: Pass true
to turn off the client-side cache. The cache is persisted to localStorage
and persists across page loads. Even with an enabled cache, all flags will get revalidated in stale-while-revalidate
fashion.useFlags
useFlag(options)
options.user
(object) optional: A user to load the flags for. A user must at least have a key
. See the supported user attributes here. The user information you pass can be used for individual targeting or rules. You can set the persist
attribute on the user to store them in HappyKit for future reference.options.traits
(object) optional: An object which you have access to in the flag's rules. You can target users based on traits.options.initialState
(object) optional: In case you preloaded your flags during server-side rendering using getFlags()
, provide the returned state as initialState
. The client will then skip the first request whenever possible and use the provided flags instead. This allows you to get rid of loading states and on the client.options.revalidateOnFocus
(object) optional: By default the client will revalidate all feature flags when the browser window regains focus. Pass revalidateOnFocus: false
to skip this behaviour.options.disableCache
(boolean) optional: The client will not cache the flags in localStorage when this setting is enabled.This function returns an object we usually call flagBag
. It contains the requested flags and other information.
flagBag
This object is returned from useFlags()
.
flags
(object): The loaded feature flags, with the defaults applied.visitorKey
(string | null): The visitor key the feature flags were fetched for.settled
(boolean): Unless you are providing initialState
, the client will need to fetch the feature flags from the API. In some cases, during static site generation, it will even need to fetch the feature flags from the API even though you provided initialState
. The settled
value will turn true
once the flags have settled on the client. This means that the only way for the value of the flags to change from then on would be if you changed one of the feature flags, or provided different inputs (user
, traits
). Once settled
has turned true, it will not turn back to false. You can use settled
in case you want to wait for the "final" feature flag values before kicking of code splitting (or showing UI).fetching
(boolean): This is true
whenever the client is loading feature flags. This might happen initially, on rerenders with changed inputs (user
, traits
) or when the window regains focus and revaldiation is triggered. You probably want to use settled
instead, as settled
stays truthy once the flags were loaded, while fetching
can flip multiple times.Provide any of these attributes to store them in HappyKit. You will be able to use them for targeting specific users based on rules later on (not yet available in HappyKit Flags).
key
(string) required: Unique key for this useremail
(string): Email-Addressname
(string): Full name or nicknameavatar
(string): URL to users profile picturecountry
(string): Two-letter uppercase country-code of user's county, see ISO 3166-1persist
(boolean): This is a special attribute which tells HappyKit to persist that user in HappyKit. When you persist a user, you can see that users profile on happykit.dev. Note that persisting users will incur additional charges in the future.getFlags
getFlags(options)
options.context
(object) required: The context which you receive from getStaticProps
or getServerSideProps
.options.user
(object) optional: Same as user
in useFlags()
. If pass a user here, make sure to pass the same user to useFlags({ user })
.options.traits
(object) optional: Same as traits
in useFlags()
. If pass traits here, make sure to pass the same traits to useFlags({ traits })
.This function returns a promise resolving to an object that looks like this:
{
flags: { /* Evaluated flags, combined with the configured fallbacks */ },
loadedFlags: { /* Evaluated flags, as loaded from the API (no fallbacks applied) */ },
initialFlagState: { /* Information about the loaded flags, which can be provided to useFlags({ initialState }) */ }
You can provide a user
as the first argument. Use this to enable per-user targeting of your flags. A user
must at least have a key
property.
// pages/foo.js
import { useFlags } from "@happykit/flags/client";
export default function FooPage(props) {
const flagBag = useFlags({ user: { key: 'user-id' } });
return flagBag.flags.xzibit ? 'Yo dawg' : 'Hello';
}
Or if you're using prerendering
// pages/foo.js
import { useFlags } from "@happykit/flags/client";
import { getFlags } from "@happykit/flags/server";
export const getServerSideProps = async (context) => {
const user = { key: 'user-id' };
const { initialFlagState } = await getFlags({ context, user });
return { props: { user, initialFlagState } };
};
export default function FooPage(props) {
const flagBag = useFlags({
user: props.user,
initialState: props.initialFlagState,
});
return flagBag.flags.xzibit ? 'Yo dawg' : 'Hello';
}
See all supported user attributes
You can configure application-wide default values for flags. These defaults will be used while your flags are being loaded (unless you're using server-side rendering). They'll also be used as fallback values in case the flags couldn't be loaded from HappyKit.
// _app.js
import { configure } from "@happykit/flags/config";
configure({
envKey: process.env.NEXT_PUBLIC_FLAGS_ENVIRONMENT_KEY,
defaultFlags: { xzibit: true },
});
Being able to set initial flag values is what enables rehydration when using server-side rendering. When you pass in initialState
the flags will be set from the beginning. This is avoids the first request on the client.
// pages/foo.js
import { useFlags } from "@happykit/flags/client";
import { getFlags } from "@happykit/flags/server";
export const getServerSideProps = async (context) => {
const { initialFlagState } = await getFlags({ context });
return { props: { initialFlagState } };
};
export default function FooPage(props) {
const { flags } = useFlags({ initialState: props.initialFlagState });
return flags.xzibit ? 'Yo dawg' : 'Hello';
}
// pages/foo.js
import { useFlags } from "@happykit/flags/client";
import { getFlags } from "@happykit/flags/server";
export const getStaticProps = (context) => {
const initialFlagState = await getFlags({ context });
return { props: { initialFlagState } };
};
export default function FooPage(props) {
const { flags } = useFlags({ initialState: props.initialFlagState });
return flags.xzibit ? 'Yo dawg' : 'Hello';
}
You don't even need to use useFlags
in case you're regenerating your site on flag changes anyways.
HappyKit will soon be able to trigger redeployment of your site when you change your flags by calling a Deploy Hook you specify.
// pages/foo.js
import { getFlags } from "@happykit/flags/server";
export const getStaticProps = () => {
const initialFlags = await getFlags();
return { props: { initialFlags } };
};
export default function FooPage(props) {
return props.flags.xzibit ? 'Yo dawg' : 'Hello';
}
The upside of this approach is that useFlags
isn't even shipped to the client. This keeps the page extremly small, as no client-side JS is added.
For use with getStaticProps
the downside is that the new flags are only available once your site is redeployed. You will be able to automate redeployments on flag changes with Deploy Hooks on happykit.dev soon.
Note that when you use getFlags()
with getStaticProps
the static generation phase has no concept of a visitor, so rollouts based on visitor information are not possible. You can still use getStaticProps
, but you should also use useFlags()
in such cases.
For use with getServerSideProps
the downside is that flag changes are only shown when the page is reloaded.
HappyKit uses the browser's visibilitychange
event to revalidate feature flags when the active window regains visibility. If you explicitly set revalidateOnFocus
to false
, then HappyKit will no longer revalidate. This can be useful if you want to reduce the number of requests to HappyKit.
// pages/foo.js
import { useFlags } from "@happykit/flags/client";
import { getFlags } from "@happykit/flags/server";
export const getStaticProps = (context) => {
const { initialFlagState } = await getFlags({ context });
return { props: { initialFlagState } };
};
export default function FooPage(props) {
const flagBag = useFlags({
revalidateOnFocus: false,
initialState: props.initialFlagState
});
return flagBag.flags.xzibit ? 'Yo dawg' : 'Hello';
}
This demo shows the full configuration with server-side rendering and code splitting.
// _app.js
import App from 'next/app';
import { configure } from "@happykit/flags/config";
configure({ envKey: process.env.NEXT_PUBLIC_FLAGS_ENVIRONMENT_KEY });
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
// pages/profile.js
import * as React from 'react';
import { useFlags } from "@happykit/flags/client";
import { getFlags } from "@happykit/flags/server";
import dynamic from 'next/dynamic';
const ProfileVariantA = dynamic(() => import('../components/profile-a'));
const ProfileVariantB = dynamic(() => import('../components/profile-b'));
export const getServerSideProps = async (context) => {
// preload your user somehow
const user = await getUser(context.req);
// pass the user to getFlags to preload flags for that user
const { initialFlagState } = await getFlags({ context, user });
return { props: { user, initialFlagState } };
};
export default function Page(props) {
const flagBag = useFlags({
user: props.user,
initialState: props.initialFlagState,
});
// The flags will always start as settled when you pass in initialState from
// `getServerSideProps`. When you use `getStaticProps`, the flags will not
// start as settled, since static rendering has no concept of a visitor.
//
// So the check for "settled" is unnecessary in this example, but useful if
// you want to use `getStaticProps`.
if (!flagBag.settled) return null
return flagBag.flags.profileVariant === 'A' ? (
<ProfileVariantA user={props.user} />
) : (
<ProfileVariantB user={props.user} />
);
}
@happykit/flags
includes type definitions. By default, flags returned from useFlags
and getFlags
have the following type:
type Flags = { [key: string]: boolean | number | string | null };
You can use @happykit/flags
without further configuration and get pretty good types.
However, all exported functions accept an optional generic type, so you can harden your flag definitions by defining a custom flag type. This allows you to define flag values explicitily.
// types/AppFlags.ts
// Define the types of your app's flags
export type AppFlags = {
booleanFlag: boolean;
numericFlag: number;
textualFlag: string;
// You can lock textual and numeric flag values down even more, since
// you know all possible values:
// numericFlag: 0 | 10;
// textualFlag: 'profileA' | 'profileB';
};
// _app.tsx
import { configure } from "@happykit/flags/config";
// import your custom AppFlags type
import { AppFlags } from "../types/AppFlags";
// the types defined in "configure" are used to check "defaultFlags"
configure<AppFlags>({
endpoint: 'http://localhost:8787/api/flags',
envKey: 'flags_pub_272357356657967622',
defaultFlags: {
booleanFlag: true,
numericFlag: 10,
textualFlag: 'profileA',
},
});
// pages/SomePage.tsx
import { useFlags } from "@happykit/flags/client";
import { getFlags } from "@happykit/flags/server";
import { AppFlags } from "../types/AppFlags";
export async function getServerSideProps(context) {
// Pass your AppFlags type when calling getFlags()
const { flags, initialFlagState } = await getFlags<AppFlags>({ context });
flags.booleanFlag; // has type "boolean"
flags.numericFlag; // has type "number"
flags.textualFlag; // has type "string"
return { props: { initialFlagState } };
}
export default function SomePage(props) {
// Pass your AppFlags type when calling useFlags()
const { flags } = useFlags<AppFlags>({ initialState: props.flags });
flags.booleanFlag; // has type "boolean"
flags.numericFlag; // has type "number"
flags.textualFlag; // has type "string"
return <div>{JSON.stringify(flags, null, 2)}</div>;
}
If you have two variants for a page and you only want to render one depending on a feature flag, you're able to keep the client-side bundle small by using dynamic imports.
import * as React from 'react';
import { useFlags } from "@happykit/flags/client";
import { getFlags } from "@happykit/flags/server";
import dynamic from 'next/dynamic';
const ProfileVariantA = dynamic(() => import('../components/profile-a'));
const ProfileVariantB = dynamic(() => import('../components/profile-b'));
export default function Page(props) {
const flagBag = useFlags({ user: { key: 'user_id_1' } });
// display nothing until we know for sure which variants the flags resolve to
if (!flagBag.settled) return null;
return flagBag.flags.profileVariant === 'A' ? (
<ProfileVariantA user={props.user} />
) : (
<ProfileVariantB user={props.user} />
);
}
You can even go one step further and preload the flags on the server, so that the client receives a prerenderd page.
Notice that the loading state is gone with that as well, since the flags are available upon the first render.
// with server-side flag preloading
import * as React from 'react';
import { useFlags, getFlags, Flags } from '@happykit/flags';
import dynamic from 'next/dynamic';
const ProfileVariantA = dynamic(() => import('../components/profile-a'));
const ProfileVariantB = dynamic(() => import('../components/profile-b'));
export const getServerSideProps = async (context) => {
// preload your user somehow
const user = await getUser(context.req);
// pass the user to getFlags to preload flags for that user
const { initialFlagState } = await getFlags({ context, user });
return { props: { user, initialFlagState } };
};
export default function Page(props) {
const flagBag = useFlags({
user: props.user,
initialState: props.initialFlagState,
});
return flagBag.flags.profileVariant === 'A' ? (
<ProfileVariantA user={props.user} />
) : (
<ProfileVariantB user={props.user} />
);
}
This technique of removing the loading state works only with getServerSideProps
. If you use getStaticProps
, the server has no concept of the current visitor, but a visitor could influence flag rollouts. The client thus needs to reevaluate the flags and will only settle (pass settled: true
) once the client-side reevaluation has completed.
FAQs
Feature Flags for Next.js
The npm package @happykit/flags receives a total of 4,611 weekly downloads. As such, @happykit/flags popularity was classified as popular.
We found that @happykit/flags demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
TypeScript is porting its compiler to Go, delivering 10x faster builds, lower memory usage, and improved editor performance for a smoother developer experience.
Research
Security News
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
Security News
Socket CEO Feross Aboukhadijeh discusses the open web, open source security, and how Socket tackles software supply chain attacks on The Pair Program podcast.