Next.js 13
This guide explains you how to implement Flagship with nextJS 13
Next.js is a full-stack framework for building web applications using react components. With Next.js, you have different options for rendering your app's content. each rendering option may require a different approach for successful Flagship integration.
Component-level rendering
With the release of React 18, we now have the ability to perform component-level rendering on both the client and server-side.
- Client component: This type of component can only be rendered on the browser side. This may be due to a specific requirement or because it uses State and Lifecycle Effects, adds interactivity and event listeners, uses browser-only APIs, or uses custom hooks that depend on state, effects, or browser-only APIs.
- Server component: This type of component is rendered by React on the server-side and then streamed to the browser.
Rendering in Next.js 13
With Next.js, you have two options for rendering content, depending on your needs. All these rendering options can be used at the same time in the same application or even in the same page.
Here are rendering options:
- Static Rendering : Your components can be pre-rendered at build time then stored for reuse on subsequent requests
Server and Client Components are rendered differently during Static Rendering:
Client Components have their HTML and JSON pre-rendered and cached on the server. The cached result is then sent to the client for hydration.
Server Components are rendered on the server by React, and their payload is used to generate HTML. The same rendered payload is also used to hydrate the components on the client, resulting in no JavaScript needed on the client.https://nextjs.org/docs/app/building-your-application/rendering#static-rendering
- Dynamic rendering: Your Components are rendered on the server at request time and no result cache is done.
Flagship implementation
To successfully implement Flagship in a Next.js 13 application, follow these five steps:
- Use the Javascript SDK in server components.
- (optional) Configure your cache revalidation settings.
- Use the React SDK in client components.
- Initialize the React SDK with initial flags data to prevent flickering in client components during the initial page load.
- Set up automatic rebuilding of your app if you are using static export
1. Use Javascript SDK in server components
Depending on your needs if you want to get a flag or do something else from a server component, the Javascript SDK is the one you need.
Inside we initialize the SDK, create visitor, Fetch flags and use the visitor instance to get a flag. Its use does not cause any flickering effect.
Information
Javascript SDK can be imported from React SDK package, so you don't need to install Javascript SDK alongside
import { Flagship } from '@flagship.io/react-sdk'
//src/helper/flagship.js
import { Flagship, FSSdkStatus, DecisionMode, LogLevel } from '@flagship.io/react-sdk'
// Function to start the Flagship SDK Decision API mode
export function startFlagshipSDK() {
if (Flagship.getStatus() && Flagship.getStatus() !== FSSdkStatus.SDK_NOT_INITIALIZED) {
return Flagship; // If it has been initialized, return early
}
return Flagship.start(process.env.NEXT_PUBLIC_ENV_ID, process.env.NEXT_PUBLIC_API_KEY, {
fetchNow: false, // Do not fetch flags immediately
decisionMode: DecisionMode.DECISION_API, // set decision mode : DECISION_API
});
}
export async function getFsVisitorData(visitorData) {
// start the SDK in Decision Api mode et get the Flagship instance
const flagship = startFlagshipSDK()
// Create a visitor
const visitor = flagship.newVisitor({
visitorId: visitorData.id,
hasConsented: visitorData.hasConsented,
context: visitorData.context
});
// Fetch flag values for the visitor
await visitor.fetchFlags();
// Return visitor instance
return visitor;
}
//src/app/server-component/page.js
import { getFsVisitorData } from "@/helpers/flagship"
export default async function ServerComponent() {
//visitor data
const visitorData = {
id: "visitorId",
hasConsented: true,
context: {
key: "value"
}
}
//Get visitor instance
const visitor = await getFsVisitorData(visitorData)
//Get flag `btnColor`
const flag = visitor.getFlag("btnColor")
const flagValue = flag.getValue("#dc3545")
return (
<>
<h1>This page is a server component</h1>
<p>flag key: btnColor</p>
<p>flag value: {flagValue}</p>
<button style={{ background: flagValue }} >Click me</button>
</>
)
}
Implementing of Bucketing Mode
We have created a Promise that starts the SDK in Bucketing mode. The Promise is resolved once the first bucketing file request call responds, even if the request fails. This is because the bucketing polling has already started, so another bucketing request will be performed in 10 seconds.
Information
The initial request may take a little longer to process, but subsequent requests will be processed almost instantly.
//src/helper/flagship.js
import { Flagship, FSSdkStatus, DecisionMode, LogLevel } from '@flagship.io/react-sdk'
// Function to start the Flagship SDK in Bucketing mode
function startFlagshipSDKAsync() {
// Return a new Promise
return new Promise((resolve) => {
// Check if the Flagship SDK has already been initialized
if (
Flagship.getStatus() &&
Flagship.getStatus() !== FSSdkStatus.SDK_NOT_INITIALIZED
) {
// If it has been initialized, resolve the Promise with the Flagship object and return early
resolve(Flagship);
return;
}
// If the SDK has not been initialized, start it with the specified parameters
Flagship.start(
process.env.NEXT_PUBLIC_ENV_ID, // Environment ID
process.env.NEXT_PUBLIC_API_KEY, // API key
{
pollingInterval: 10, // Set polling interval to 10
fetchNow: false, // Do not fetch flags immediately
decisionMode: DecisionMode.BUCKETING, // Set decision mode to BUCKETING
onBucketingSuccess: () => {
// It is triggered when the first bucketing file request succeeds, resolve the Promise with the Flagship object
resolve(Flagship);
},
onBucketingFail() {
// It is triggered when the first bucketing file request fails, resolve the Promise with the Flagship object
resolve(Flagship);
},
}
);
});
}
export async function getFsVisitorData(visitorData) {
// start the SDK in Bucketing mode et get the Flagship instance
const flagship = await startFlagshipSDKAsync()
// Create a visitor
const visitor = flagship.newVisitor({
visitorId: visitorData.id,
hasConsented: visitorData.hasConsented,
context: visitorData.context
});
// Fetch flag values for the visitor
await visitor.fetchFlags();
// Return visitor instance
return visitor;
}
2. Configure your cache revalidation settings (optional)
Flagship uses HTTP calls to communicate with its servers. With Next.js’s built-in support for caching data, some Flagship routes could be cached and deduplicated, which could negatively impact the visitor experience. By default, all SDK routes are revalidated after 20 seconds, but you can update this value using the nextFetchConfig
option during SDK initialization. The SDK uses per-request caching, so only SDK route calls will be affected by this setting.
//src/helper/flagship.js
import { Flagship, FlagshipStatus, DecisionMode, LogLevel } from '@flagship.io/react-sdk'
// Function to start the Flagship SDK
export function startFlagshipSDK() {
if (Flagship.getStatus() && Flagship.getStatus() !== FlagshipStatus.NOT_INITIALIZED) {
return Flagship; // If it has been initialized, return early
}
return Flagship.start(process.env.NEXT_PUBLIC_ENV_ID, process.env.NEXT_PUBLIC_API_KEY, {
fetchNow: false, // Do not fetch flags immediately
decisionMode: DecisionMode.DECISION_API, // set decision mode : DECISION_API
nextFetchConfig: { revalidate: 15 }, //Set cache revalidation for SDK routes to 15 seconds
});
}
export async function getFsVisitorData(visitorData) {
// start the SDK in Decision Api mode et get the Flagship instance
const flagship = startFlagshipSDK()
// Create a visitor
const visitor = flagship.newVisitor({
visitorId: visitorData.id,
hasConsented: visitorData.hasConsented,
context: visitorData.context
});
// Fetch flag values for the visitor
await visitor.fetchFlags();
// Return visitor instance
return visitor;
}
3. Use React SDK in client component
The React SDK should be initialized in the Root Layout
or another top-level component. It is important that the SDK is initialized in a component that is higher in the hierarchy than any components that need to consume the SDK.
//src/app/layout.js
import './globals.css'
import { FlagshipProvider } from '@flagship.io/react-sdk'
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({ children }) {
const visitorData = {
id: "visitorId",
hasConsented: true,
context: {
key: "value"
}
}
return (
<html lang="en">
<body >
<FlagshipProvider
envId={process.env.NEXT_PUBLIC_ENV_ID}
apiKey={process.env.NEXT_PUBLIC_API_KEY}
visitorData={visitorData}>
<div className={"container"}>
<main className={"main"}>
{children}
</main>
</div>
</FlagshipProvider>
</body>
</html >
)
}
// src/app/client-component/page.js
'use client'
import { useFsFlag } from "@flagship.io/react-sdk";
export default function ClientComponent() {
// Get the flag `btnColor` using useFsFlag hook
const flag = useFsFlag("btnColor")
cosnt flagValue = flag.getValue("#dc3545")
return (
<>
<h1>This page is client component</h1>
<p>flag key: btnColor</p>
<p>flag value: {flagValue}</p>
<button style={{ background: flagValue }} >Click me</button>
</>
)
}
4. Initialize React SDK with initial flags data to avoid flickering in client components
Client components fetch flags from the browser, when rendered, flag.getValue()
first returns the default value and the the flag value after fetchFlags completes. So, the user could first see the flag's default value and then the value returned by Flagship causing a flickering effect that can impact the user experience.
To avoid this, either at build time or on the first request time, during initialization, you can feed the SDK with the same flag data that it would fetch from the browser side.
//src/app/layout.js
import { getFsVisitorData } from '@/helpers/flagship'
import './globals.css'
import { FlagshipProvider } from '@flagship.io/react-sdk'
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default async function RootLayout({ children }) {
const visitorData = {
id: "visitorId",
hasConsented: true,
context: {
key: "value"
}
}
// Get visitor instance
const visitor = await getFsVisitorData(visitorData)
return (
<html lang="en">
<body >
<FlagshipProvider
envId={process.env.NEXT_PUBLIC_ENV_ID}
apiKey={process.env.NEXT_PUBLIC_API_KEY}
initialFlagsData={visitor.getFlags().toJSON()} // set Initial flags data from visitor instance
visitorData={visitorData} // visitor data
>
<div className={"container"}>
<main className={"main"}>
{children}
</main>
</div>
</FlagshipProvider>
</body>
</html >
)
}
5. Set up automatic rebuilding of your app if you are using static export
Static Exports in Next.js 13 allow you to generate a fully static version of your application, which can then be deployed and hosted on any web server capable of serving HTML/CSS/JS static assets.
This means that if your campaigns have been updated from Flagship, you may need to rebuild your application to update its content.
To automate the rebuilding of your application, you need to use Flagship’s Webhooks Integrations. When your campaigns are updated in Flagship, a webhook called [environment] synchronized will be triggered and you can use this event to trigger a rebuild of your application to ensure that it reflects the updated flag values.
In this guide, we will deploy our app to Netlify.com, but the process is similar for other platforms and can also be used with a CI/CD pipeline.
We will proceed in two steps:
1. Once your app is deployed to netlify.com, go to Site Settings -> Build and Deploy and create a build hook.
2. Set up the webhook on the Flagship platform
On the Flagship platform, go to Settings -> Integrations and select the Webhooks tab. Choose the [environment] synchronized event and enter the URL for your Netlify build hook. Then click “Create” to set up the webhook.
Do you have any feedback or suggestions? Would you like to see another tutorial? Contact us with your thoughts [email protected].
You can view the full code for this example on github
Updated 5 months ago