Using LaunchDarkly with Next.js
Read time: 18 minutes
Last edited: Oct 03, 2024
Overview
This guide explains how to connect LaunchDarkly with Next.js on both the client and server side.
Next.js is a React-based framework for full-stack JavaScript development. One of the most powerful aspects of Next.js is that it offers tools that let you render content at multiple junctures: at build time; on the server; or on the client. It even offers an option for deferred rendering that builds pages only when they are first requested.
While these options add a lot of power, they can also create some confusion if you want to integrate Next.js and LaunchDarkly. Which library do you need? Where do you import and configure it? How do you use it once it is set up?
In this tutorial, we'll answer these questions for the different types of integrations Next.js offers.
Rendering options in Next.js
In the past, rendering occurred in just one place:Â the server. In response to a page request, the server would return HTML that a browser could render. Things become more complicated when you use a "full stack" framework like Next.js, which is sometimes referred to as an isomorphic JavaScript framework. This framework doesn't only render content on the client with JavaScript (in this case, the client is a browser). It also renders on the backend using JavaScript within Node.js.
There are four different strategies for rendering content in a Next.js application. Keep in mind that an application could have any mix of any or all of these and, in some cases, even a single page may include multiple rendering strategies for different parts of the page.
The four strategies are:
- Pre-rendering (static site rendering, static site generation, or SSG): This content is rendered at build time before the application is deployed. This usually happens as part of a CI/CD build process. For example, new code checked into your git repository triggers a build in Netlify or Vercel, two popular deployment options for Next.js sites. A build hook or deploy hook can also trigger a new build. A common example is when you're using an external headless content management system so that changes to content are automatically re-rendered in the build.
- Server-side rendering (SSR):Â This is the traditional style of rendering a page. The content is generated on the server (Node.js in this case) for every single request and then sent to the browser for the initial page load.
- Client-side rendering:Â Some content, such as context-specific data on a pre-rendered page, cannot be fully rendered before deployment. In these cases, you must use JavaScript to modify the rendered output in the browser. Similarly, you may not want to re-render the page server-side for every interaction. In these cases, you'd also update the page using client-side JavaScript.
- Deferred rendering:Â This is functionally equivalent to pre-rendering except that the requested page isn't rendered during the initial build. Instead, it's rendered only when the first context requests it. For example, pre-rendering an e-commerce site with thousands of product pages could cause excessively long builds for little value, because many of those pages may get minimal traffic. Instead, you can defer rendering these pages until they are first requested. While the initial visitor may experience a slight delay in loading the page, subsequent visitors will receive the page from the build cache.
Each of these types of rendering has different implications when you're working with a LaunchDarkly SDK.
There are two forms of deferred rendering: Incremental Static Regeneration (ISR) and Distributed Persistent Rendering (DPR). The key difference is that ISR can include a timeout, meaning that the generated page will go stale and re-render after a specified period. Whereas with DPR, the rendered page becomes part of the build and the only way to re-render it is with a new build.
Which SDK do I use?
LaunchDarkly offers multiple JavaScript SDKs that may be relevant to a Next.js project:
So, which one should you use? That depends on the flag you are integrating and the types of rendering it is affected by.
Consider these factors:
- For pre-rendering, server-side rendering, and deferred rendering, use the server-side Node.js SDK. Although each of the rendering types are very different, they all happen on the server in one manner or another, even if that server is a build server generating static pages.
- For client-side rendering, use the React Web SDK. Next.js is a React meta-framework, meaning that it is a framework built on top of React. One of the benefits is that the React Web SDK offers the React hooks integration that is available in Next.js.
In many cases, a Next.js application needs both the server-side Node.js SDK and the React Web SDK to use flags on both the frontend and backend.
Consider build impact
In some cases, flipping a flag has immediate impact, but in others it may require a site rebuild before a change appears. Let's explore the different rendering options, when you'll need to trigger a rebuild, and how to do that.
First, when do you need a rebuild? You'll need to trigger a rebuild for:
- Pre-rendered pages: If your flag impacts the content or functionality on any pre-rendered page, the changes it has won't be visible until a rebuild triggers and deploys.
- Deferred rendered pages: If a page's rendering is deferred using DPR or ISR, you must trigger a full rebuild, because you do not have an easy way to determine if the impacted pages have been built and already exist in the cache. The only way to ensure that the page updates appropriately is to do a full site rebuild. A page that uses ISR can have a cache timeout and rebuild to reflect any changes after the timeout expires, but this still requires waiting for that page's cache to be invalidated and then rebuilt for the next context's request. It's better and safer to initiate a full rebuild and confirm your changes appear.
Server-side rendered pages will reflect any changes on the next request. If an end user is already on the page, they must refresh it to view the impact of a flag change. Client-side rendered pages will immediately reflect any changes in the browser without any refresh.
Automate rebuilds
You do not need to manually trigger a rebuild each time you make a flag change. Instead, you can use build hooks on your deployment platform to automate the process. For example, here's Netlify's build hook documentation or Vercel's deploy hook documentation with a combination of tags and webhooks on LaunchDarkly.
First, you must add a tag. You can add this when you create a new feature flag that will need to trigger a rebuild. For example, you can name the tag build-impact
. You can name yours whatever you like but it's best to keep it descriptive and consistent. Use the same tag for all flags that need to trigger a rebuild.
Here is what the flag will look like:
You add the tag so that you can use it to filter only those flags that require a full site rebuild to trigger the webhook. This means that if a tag is only used in SSR or client-side rendering, it won't cause a rebuild. This prevents needless deploys and saves you money, because most deployment platforms ultimately charge for build minutes.
Next, you must connect your tagged flag so it triggers a rebuild. The following examples show how to connect LaunchDarkly to Netlify's build hooks and Vercel's deploy hooks, but most deployment platforms offer a similar type of feature.
Create a build hook on Netlify
In the Netlify admin dashboard:
- Click on the site you would like to connect the webhook to.
- Navigate to Site Settings, then Build & Deploy, then Build Hooks.
- Click the button to add a new build hook and give it a name (for example, "LaunchDarkly").
- Choose the git branch that will be used as the source when this build hook is called.
Netlify allows branch deploys. You could use this to let flags from your test environment within LaunchDarkly trigger a deploy on a test branch in Netlify. For this example you can just choose the main
branch, which typically deploys the public site.
- After you add the hook, a URL for the new hook appears. Copy the URL and save it.
Create a deploy hook in Vercel
In the Vercel admin dashboard:
- Click on the site you would like to connect the webhook to.
- Navigate to Settings, then Git, then Deploy Hooks.
- Give the deploy hook a human-readable name and specify the git branch to use.
- Click Create Hook. After the deploy hook is created, copy the URL. You will need it to connect the deploy hook to LaunchDarkly.
Add the build/deploy hook in LaunchDarkly
This procedure requires a build hook from Netlify or a deploy hook from Vercel. You can use either type of hook interchangeably in this procedure.
- In LaunchDarkly, navigate to the Integrations page.
- Find the the "Webhooks" section and click Add integration:
- Give your webhook a human-readable name, and then paste the URL of the build/deploy hook you just created in Netlify or Vercel:
Next, you must add a policy to this webhook. The policy determines the permissions that the webhook has. This will ultimately limit the access it has to events triggered by changes to your LaunchDarkly flags and settings. This means the webhook will only call the build hook on those events it has access to. In this case, you only want it to respond to flags that are tagged build-impact
.
To add a policy to the webhook:
-
Navigate to the "Policy" section of the webhook and click Advanced editor. This lets you create a policy with very specific restrictions. The example code below says that this webhook has access to the environments (
env/*
) and flags (flag/*
) tagged withbuild-impact
. -
Paste this code into the advanced editor. Change the project key from
proj-key-123abc
to your LaunchDarkly project key. To learn how to find your project key, read Project keys.Here is the code to paste and modify:
[{"effect": "allow","actions": ["*"],"resources": ["proj/proj-key-123abc:env/*:flag/*;build-impact"]}] -
Click Save settings.
To test this policy, create a flag and tag it with build-impact
. Now change its value and then go to your Netlify or Vercel dashboard. You should observe a new build starting. Next, create a flag and do not tag it. Change its value and check your Netlify or Vercel dashboard. Nothing will change.
Integrate LaunchDarkly's SDKs into Next.js
Here are some examples of how to integrate the LaunchDarkly SDKs into Next.js applications. These examples show how to add flags into each of the different types of rendering scenarios: SSG, SSR, client-side rendering and ISR/DPR (also known as deferred rendering).
The code shown in these examples is available on GitHub. You can also observe the project running on Netlify and Vercel. The application uses the DEV API to populate some content.
Create a server-side component
First, you can create a helper component for any of the server-side rendering methods. Keep in mind that the server-side rendering methods include more than just SSR. Static rendering and deferred rendering both occur on the "server" in that their code runs on the backend. The key difference is that they render their pages as static files only once, and SSR renders the page on each request.
You can use this component for:
- Static rendering
- Server-side rendering
- Deferred rendering
This component handles the initialization and configuration for the Node.js server-side SDK so you don't need to do this on every page that uses a LaunchDarkly flag. This component appears in the project as /lib/ld-server.js
, but you can place it anywhere and name it whatever you like.
The component has two methods:
- The
initialize()
method passes an API key to the SDK and then waits for the initialization process to be completed. - The
getClient()
method checks if thelaunchDarklyClient
has been initialized and returns it if it has or calls theinitialize()
method if it hasn't.
Any page that uses this component only needs the getClient()
method, which always returns an initialized LaunchDarkly SDK client.
Here is an example method:
import * as LaunchDarkly from '@launchdarkly/node-server-sdk';let launchDarklyClient;async function initialize() {launchDarklyClient = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY);try {await launchDarklyClient.waitForInitialization({timeout: 10});} catch (err) {// timeout or initialization failure}}export async function getClient() {if (launchDarklyClient){try {await launchDarklyClient.waitForInitialization({timeout: 10});} catch (err) {// timeout or initialization failure}return launchDarklyClient;}await initialize();return launchDarklyClient;}
You must have a LAUNCHDARKLY_SDK_KEY
environment variable set in a .env.local
file. In addition, you must ensure that wherever you deploy this application, that environment variable is set as well.
For example, if you are deploying to Netlify:
- Within the site's settings on the dashboard, go to Build & Deploy, then Environment.
- Click Edit Variables.
- Click Add Variable.
- Add the
LAUNCHDARKLY_SDK_KEY
variable.
Alternatively, if you are deploying to Vercel:
- Open the site in Vercel's dashboard and go to Settings, then Environment Variables.
- Give the variable a name and value and then choose the environments that you would like it to apply to.
- Click Add. This saves the environment variable.
To get your LaunchDarkly SDK key, go to Account settings, then Projects. Choose your project and then copy the SDK key for the environment you wish to work with.
Static rendering (SSG)
Here are some use cases involving pre-rendered content. In Next.js, data being passed to a pre-rendered (also known as static) page is passed by the getStaticProps()
function. If your page uses dynamic routes, you'll also need to define a getStaticPaths()
method that outputs the paths that will be rendered. The integration of LaunchDarkly in both methods is the same.
The following example uses the ld-server.js
component's getClient()
method to get the initialized LaunchDarkly client. Then it requests the value of the featured-username
key. This is a string key that is used to determine the DEV username to use to load posts from the DEV API. You could also have gotten a true / false
flag and passed that value when rendering the UI to determine if a feature should be shown.
export async function getStaticProps() {const client = await getClient();let featuredUsername = await client.variation("featured-username",{ key: "context-key-123abc" },false);const response = await fetch(`https://dev.to/api/articles?username=${featuredUsername}&page=1&per_page=10`);const data = await response.json();return {props: {featuredUsername: featuredUsername,posts: data,},};}
You could use a flag like this to modify the behavior of a page without needing to ask for code changes from the dev team.
In statically pre-rendered pages, you cannot use different variations for different contexts or roll out a variation. Every context receives the same statically rendered content. This is why you use a single constant value for the context. In this case, you can use a context key as the constant.
In the next example, you are loading two flags: show-about-us
and new-about-us
.
The show-about-us
flag relates to the client-side rendering example that comes later. The new-about-us
flag determines which file-based content (in this case a Markdown file) should be loaded and rendered.
Imagine a scenario where you wanted to give marketing the ability to launch an updated About Us page when new branding and messaging are ready to align with their campaigns. This flag allows you to do this even when the content is loaded from files in the git repository, such as if they were managed by a git-based headless CMS.
export async function getStaticProps() {const client = await getClient();let showPage = await client.variation("show-about-us",{ key: "context-key-123abc" },false);let loadPage = "";let frontMatter = "";let markdownBody = "";if (showPage) {let loadPage = await client.variation("new-about-us",{ key: "context-key-123abc" },false);const content = await import(`../content/${loadPage}.md`);const data = matter(content.default);frontMatter = data.data;markdownBody = data.content;}return {props: {frontmatter: frontMatter,markdownBody: markdownBody,showPage: showPage,},};}
In both these cases, you must trigger a rebuild before the content will update on the deployed site. Any of the flags references in either getStaticProps()
or getStaticPaths()
must have the build-impact
tag you created earlier in order to automatically trigger a rebuild when the flag changes.
Server-side rendering (SSR)
Using a flag in a server-rendered page is very similar to a statically rendered page. You still get the client from the ld-server.js
component and load the key from the SDK using the same syntax.
The key differences are:
- You load the client and pull the value of the flag within the
getServerSideProps()
method, which is the functional equivalent ofgetStaticProps()
but for SSR pages. - You don't need to trigger a rebuild when a flag changes. However, you need to refresh the page in order to observe the content change.
In this example, you are also calling a string flag that determines which post category will be featured. The value of this flag determines the parameter you pass to the DEV API.
export async function getServerSideProps() {const client = await getClient();let featuredCategory = await client.variation("featured-category",{ key: "context-key-123abc" },false);const response = await fetch(`https://dev.to/api/articles?tag=${featuredCategory}&page=1&per_page=5`);const data = await response.json();return {props: {featuredCategory: featuredCategory,posts: data,},};}
Client-side rendering
Client-side rendering is the only scenario in this guide that requires the React Web SDK. This means that it works differently than the previous examples.
The client-side SDK uses a different, more restricted key than the server-side SDKs. To get the client-side ID, go to Account settings, then Projects. Copy the ID and add a LAUNCHDARKLY_SDK_CLIENT_SIDE_ID
value to .env.local
. You also must verify that this ID exists on your deployment provider.
Environment variables are not automatically made available to the client in Next.js. In order to do that, you must alter the next.config.js
file in your project to add the code below to the module.exports
block. This code essentially says to make the LAUNCHDARKLY_SDK_CLIENT_SIDE_ID
variable available on the client using the same variable name.
Add this code to the module.exports
block:
env: {LAUNCHDARKLY_SDK_CLIENT_SIDE_ID: process.env.LAUNCHDARKLY_SDK_CLIENT_SIDE_ID,},
There are two ways to initialize the React Web SDK so that it is available throughout your application. To learn more, read the React Web "Getting started" documentation. The following examples uses the withLDProvider
method. To do this, you must have an _app.js
in the root of your /pages
directory, if one does not yet exist.
Rather than export default
the MyApp
function, wrap the application in a LaunchDarkly provider and return that.
Here is an example _app.js
:
import "../styles/globals.css";import { withLDProvider } from "launchdarkly-react-client-sdk";function MyApp({ Component, pageProps }) {return <Component {...pageProps} />;}export default withLDProvider({clientSideID: process.env.LAUNCHDARKLY_SDK_CLIENT_SIDE_ID,})(MyApp);
Now you can use your flags on the client. You can do this with the useFlags()
React hook. The following example pulls a flag, show-about-us
, and uses that to determine if the "About Us" link should be displayed within the site navigation. Because show-about-us
is not a valid JavaScript variable name, LaunchDarkly automatically converts it to camel case.
Here is an example:
import { useFlags } from "launchdarkly-react-client-sdk";import Link from "next/link";export default function Nav() {let { showAboutUs } = useFlags();return (<div className="nav"><ul><li><Link href="/">Home</Link></li><li><Link href="/ssr">SSR</Link></li>{showAboutUs && (<li className="about"><Link href="/about">About Us</Link></li>)}</ul></div>);}
Because this is running on the client, any flag changes you make in LaunchDarkly appear in the UI almost immediately, without requiring a page refresh.
For this particular example, there's a complication: we've removed the link to the About Us page from the UI, but the route for /about
is still generated. This means that, if someone knew or figured out the path, they could still access the page. What if it was critical that this page not be accessible when show-about-us
returns false?
You can make a few small changes to the /pages/about.js
file. First, load the value within the page's getStaticProps()
method.
Here's how:
let showPage = await client.variation("show-about-us",{ key: "context-key-123abc" },false);
And then pass that value as part of the page's props:
return {props: {frontmatter: frontMatter,markdownBody: markdownBody,showPage: showPage,},};
When the context hits this route but show-about-us
is false, you can make it return a 404, as if the page doesn't even exist.
To do that, you must first import Next's error page:
import ErrorPage from "next/error";
Now you can access the showPage
prop that you passed and use it to return a 404.
Here is an example:
export default function About({ frontmatter, markdownBody, showPage }) {if (!showPage) return <ErrorPage statusCode="404" />;return (...);}
There are two important things to consider:
- You have now introduced a build impact to the
show-about-us
flag and will need to tag it so that the site is rebuilt when that flag is changed. - Because you introduced a build impact, you cannot show different variations to different contexts. Either everyone will receive the About Us page or no one will.
Incremental static regeneration (ISR) and distributed persistent rendering (DPR)
Because ISR and DPR are essentially just deferred static rendering, the integration is identical to the SSG integration. You create both an ISR or a DPR page the same way. The main difference is that ISR can have a timeout where the page gets re-rendered without a complete rebuild. With DPR, it only ever renders once per build. On Netlify, if you do not provide a timeout, it runs as DPR, but if you do, it runs as if it is SSR, as Netlify does not support ISR. Vercel, the creators of Next.js, has full support for ISR.
The example below uses a dynamic "catch all" route as /dpr/[[slug]].js
. The folder name /dpr
is just for example purposes and not required to use DPR. This page pulls the top five posts from the featured author on the DEV API site, as determined by the featured-username
flag, and prerenders those. This is determined by only adding these five paths to the paths
variable returned by getStaticPaths()
.
This page uses DPR/ISR because of the fallback: "blocking"
line on the return instead of fallback: false
. In a typical prerendered static path, fallback: false
causes any path not returned in the paths
variable to return a 404. However, with fallback: "blocking"
, any path not in the paths
variable waits for the page to be generated and return that to the browser. To learn more, read the Next.js documentation.
Here is an example:
export async function getStaticPaths() {const client = await getClient();let username = await client.variation("featured-username",{ key: "context-key-123abc" },false);// only grab the first 5 to prerenderconst top = 5;const response = await fetch(`https://dev.to/api/articles?username=${username}&page=1&per_page=${top}`);const data = await response.json();const paths = data.map((post) => {let username = post.organization? post.organization.username: post.user.username;let slug = `/dpr/${username}/${post.slug}`;return slug;});return {paths: paths,fallback: "blocking",};}
This route functions for any valid path even if it is not one of the top five that were returned in getStaticPaths()
. You can view this in action by going to the deployed example sites on Netlify or Vercel and clicking one of the blog post links past the first five. The page will still render, though you may notice a slight delay compared to the first five.
The key thing to remember when you think about integrating with LaunchDarkly is that, regardless of whether the page uses DPR or ISR, the only way to ensure that the flag changes the page is to trigger a rebuild. Otherwise, your end users may receive the incorrect version of the page until a new build is triggered with DPR or until the timeout has triggered a rebuild of the page with ISR.
Conclusion
Combining Next.js with LaunchDarkly's feature management can be incredibly powerful. We only discussed a handful of possibilities here but hopefully this has already gotten you thinking about more.
To summarize, the key items to consider are:
- What type of rendering are you integrating your flag into? SSG, SSR, client-side or DPR/ISR?
- Which SDK do you need? Server-side Node.js should work for all cases except client-side rendering, which uses the React Web SDK.
- Does your flag have build impact? This applies to flags connected to SSG or DPR/ISR. If so, tag it properly to automatically trigger a rebuild.
Your 14-day trial begins as soon as you sign up. Get started in minutes using the in-app Quickstart. You'll discover how easy it is to release, monitor, and optimize your software.
Want to try it out? Start a trial.