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