Server-side Rendering Integration With Flagship [SSR]
This guide explains you how to use Flagship with nextJS using SSR mode.
Next.js is used for this example
For this example, we have used the Next.js framework.
If you need more examples, please contact us at [email protected]
How it works
In server-side rendering (SSR), content is rendered on the server for every page request.
During this process, we fetch all flags that target the visitor and pass them as props to the __app.js
component. This data is then used to populate the FlagshipProvider
component.
Note
Once your app has been loaded in a browser, you can use the Flagship SDK in the same way as you would in a client-side rendering mode.
Implementation
Setup a NextJS project
Please refer to this article link to set up a NextJS project.
Once your project has been set up, you will need to install and initialize the Flagship React SDK.
yarn add @flagship.io/react-sdk
Initialize Flagship SDK server side
You have to create a module named startFlagshipSDK.js
in the root directory of our project.
This module contains a function called startFlagshipSDK()
which is responsible for initializing and starting the Flagship SDK.
The intention is to invoke this function when the server starts up.
//startFlagshipSDK.js
// Importing required modules from the Flagship React SDK
const { DecisionMode, Flagship, FSSdkStatus } = require("@flagship.io/react-sdk");
// Function to start the Flagship SDK
function startFlagshipSDK() {
// Check if the Flagship SDK has already been initialized
if (Flagship.getStatus() && Flagship.getStatus() !== FSSdkStatus.SDK_NOT_INITIALIZED) {
return; // If it has been initialized, return early
}
// Start the Flagship SDK with the provided environment ID and API key
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 or BUCKETING
});
}
// Export the startFlagshipSDK function
module.exports = { startFlagshipSDK };
We will invoke the startFlagshipSDK()
function from within our next.config.js
file.
This file is executed whenever we run the build
, start
, or dev
commands for our Next.js application.
As such, we can use it as a convenient location to start the Flagship SDK.
Here is the refactor of next.config.js
:
//next.config.js
// Importing the startFlagshipSDK function from the startFlagshipSDK module
const { startFlagshipSDK } = require('./startFlagshipSDK');
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
};
// Exporting a function that returns the Next.js configuration object
module.exports = () => {
// Check if the 'dev' or 'start' argument is present in the process arguments
if (process.argv.includes('dev') || process.argv.includes('start')) {
startFlagshipSDK(); // If it is, start the Flagship SDK
}
return nextConfig; // Return the Next.js configuration object
};
Initialize Flagship in the project
We will customize the next component App to initialize the Flagship SDK.
//pages/__page.jsx
import { FlagshipProvider } from '@flagship.io/react-sdk'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
const { initialVisitorData, initialFlagsData } = pageProps
return (
<FlagshipProvider
envId={process.env.NEXT_PUBLIC_ENV_ID}
apiKey={process.env.NEXT_PUBLIC_API_KEY}
visitorData={initialVisitorData || {}}
initialFlagsData={initialFlagsData}
>
<Component {...pageProps} />
</FlagshipProvider>)
}
export default MyApp
Our MyApp
component will have two props: initialVisitorData
and initialFlagsData
.
The initialVisitorData
prop contains data about the visitor, while the initialFlagsData
prop contains the FlagsData that was fetched from the getServerSideProps function during a page request.
This data is used to populate the FlagshipProvider
component.
There is no need to initialize the Flagship SDK again in the getServerSideProps
function, as it has already been initialized when our Next.js application started up. Instead, we can simply create a visitor and fetch the relevant flags.
Here is the index page:
// pages/index.jsx
// Importing required modules from the Flagship React SDK
import {
useFsFlag,
useFlagship,
Flagship,
HitType,
EventCategory,
} from "@flagship.io/react-sdk";
import styles from "../styles/Home.module.css";
// Home page component
export default function Home() {
// Get the Flagship instance
const fs = useFlagship();
// Get the value of the 'my_flag_key' flag
const myFlag = useFsFlag("my_flag_key");
// Function to send a hit to Flagship
const onSendHitClick = () => {
fs.sendHits({
type: HitType.EVENT,
category: EventCategory.ACTION_TRACKING,
action: "click button",
});
};
return (
<div className={styles.container}>
<main className={styles.main}>
<h1>Next ServerSide Rendering Integration With Flagship [SSR]</h1>
<p>flag key: my_flag_key</p>
<p>flag value: {myFlag.getValue("default-value")}</p>
<button
style={{ width: 100, height: 50 }}
onClick={() => {
onSendHitClick();
}}
>
Send hits
</button>
</main>
</div>
);
}
// This function runs only on the server side
export async function getServerSideProps(context) {
const { res, req } = context;
// Get the visitor ID from the 'fs_visitorID_cookie' cookie if it exists
const fs_visitorID_cookie = req.cookies["fs_visitorID_cookie"];
// Define initial visitor data
const initialVisitorData = {
id: fs_visitorID_cookie, // If the cookie does not exist, the SDK will generate a new visitor ID
hasConsented: true,
context: {
any: "value",
},
};
// Create a new visitor using the initial visitor data
const visitor = Flagship.newVisitor({
visitorId: initialVisitorData.id,
context: initialVisitorData.context,
hasConsented: initialVisitorData.hasConsented
});
// Fetch flags for the visitor
await visitor.fetchFlags();
// Set the 'fs_visitorID_cookie' cookie with the visitor ID
res.setHeader("Set-Cookie", `fs_visitorID_cookie=${visitor.visitorId}`);
// Pass data to the page via props
return {
props: {
initialFlagsData: visitor.getFlags().toJSON(),
initialVisitorData: {
...initialVisitorData,
id: visitor.visitorId,
},
},
};
}
In the getServerSideProps
of the index component page, we :
- fetch the flags for the targeted visitor
- pass the fetched flags to the page via props
Alternate Approach to Implementing 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.
//startFlagshipSDK.js
// Function to start the Flagship SDK
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);
},
}
);
});
}
We have refactored the index.jsx
module. The startFlagshipSDKAsync
function will no longer be called in the next.config.js
file. Instead, it will be called on each page in the getServerSideProps
function.
// pages/index.jsx
// Importing required modules from the Flagship React SDK
import {
useFsFlag,
useFlagship,
HitType,
EventCategory,
} from "@flagship.io/react-sdk";
import styles from "../styles/Home.module.css";
import { startFlagshipSDKAsync } from "../startFlagshipSDK"; // Importing startFlagshipSDKAsync function
// Home page component
export default function Home() {
// Get the Flagship instance
const fs = useFlagship();
// Get the value of the 'my_flag_key' flag
const myFlag = useFsFlag("my_flag_key");
// Function to send a hit to Flagship
const onSendHitClick = () => {
fs.hit.sendHits({
type: HitType.EVENT,
category: EventCategory.ACTION_TRACKING,
action: "click button",
});
};
return (
<div className={styles.container}>
<main className={styles.main}>
<h1>Next ServerSide Rendering Integration With Flagship [SSR]</h1>
<p>flag key: my_flag_key</p>
<p>flag value: {myFlag.getValue("default-value")}</p>
<button
style={{ width: 100, height: 50 }}
onClick={() => {
onSendHitClick();
}}
>
Send hits
</button>
</main>
</div>
);
}
// This function runs only on the server side
export async function getServerSideProps(context) {
const { res, req } = context;
// Get the visitor ID from the 'fs_visitorID_cookie' cookie if it exists
const fs_visitorID_cookie = req.cookies["fs_visitorID_cookie"];
// Define initial visitor data
const initialVisitorData = {
id: fs_visitorID_cookie, // If the cookie does not exist, the SDK will generate a new visitor ID
hasConsented: true,
context: {
any: "value",
},
};
// start the SDK et get the Flagship instance
const flagship = await startFlagshipSDKAsync();
// Create a new visitor using the initial visitor data
const visitor = flagship.newVisitor({
visitorId: initialVisitorData.id,
context: initialVisitorData.context,
hasConsented: initialVisitorData.hasConsented
});
// Fetch flags for the visitor
await visitor.fetchFlags();
// Set the 'fs_visitorID_cookie' cookie with the visitor ID
res.setHeader("Set-Cookie", `fs_visitorID_cookie=${visitor.visitorId}`);
// Pass data to the page via props
return {
props: {
initialFlagsData: visitor.getFlags().toJSON(),
initialVisitorData: {
...initialVisitorData,
id: visitor.visitorId,
},
},
};
}
The initial request may take a little longer to process, but subsequent requests will be processed almost instantly.
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 6 months ago