React Router 7+ with Server-Side Rendering (SSR)
React Router is a standard library for routing in React applications that enables dynamic routing with server-side rendering capabilities. With React Router v7+, you have a powerful and flexible system for managing navigation and rendering in both server and client environments for your applications.
Understanding React Router 7+ with SSR
React Router v7 introduces a refined approach to routing with several key SSR-focused features:
Loaders: Data fetching functions that run on the server before a route renders
Actions: Server-side functions that process form submissions and data mutations
Data APIs: Methods to access loaded data throughout your application
Hydration: Seamless transition from server-rendered content to client interactivity
Route Configuration: Type-safe route definitions that support nested layouts
Flagship implementation with SSR
To successfully implement Flagship in a React Router 7+ SSR application, follow these steps:
Fetch Flagship data on the server using loader functions
Pass initial flag data and visitor information to the client
Set up the FlagshipProvider at the root level with hydration support
Access flags in components using hooks
Send analytics hits from client components
1. Set up the Flagship Provider with SSR support
Create a custom provider component that initializes Flagship with SSR hydration:
// app/helpers/FsProvider.tsx
import FlagshipProvider, {
LogLevel,
type SerializedFlagMetadata,
type VisitorData,
} from "@flagship.io/react-sdk";
export function FsProvider({
children,
visitorData,
initialFlagsData,
}: {
children: React.ReactNode;
visitorData?: VisitorData;
initialFlagsData?: SerializedFlagMetadata[];
}) {
return (
<FlagshipProvider
envId={import.meta.env.VITE_ENV_ID}
apiKey={import.meta.env.VITE_API_KEY}
logLevel={LogLevel.DEBUG}
initialFlagsData={initialFlagsData} // Hydrates client with server-fetched flags
visitorData={visitorData || null}
>
{children}
</FlagshipProvider>
);
}
2. Fetch Flagship data in a server loader function
Use React Router's server loader function to fetch initial flag data before server rendering:
export async function loader({ params }: Route.LoaderArgs) {
// This runs on the server during SSR
const visitorData = {
visitorId: "visitorId", // In production, generate or retrieve a unique ID
context: {
key: "value",
},
hasConsented: true,
};
// Initialize Flagship and fetch flags on the server
const visitor = await getFsVisitorData(visitorData);
// Return data that will be available to the client
return {
fsInitialFlagsData: visitor?.getFlags().toJSON(), // Serialized flags for hydration
visitorData, // Pass visitor data to maintain consistency
};
}
Create a helper function to initialize the Flagship SDK for server and client:
// app/helpers/flagship.ts
import { Flagship, DecisionMode, LogLevel } from "@flagship.io/react-sdk";
export async function startFlagshipSDK() {
// Works in both server and client environments
return await Flagship.start(
import.meta.env.VITE_ENV_ID,
import.meta.env.VITE_API_KEY,
{
logLevel: LogLevel.DEBUG,
fetchNow: false,
decisionMode: DecisionMode.DECISION_API,
}
);
}
export async function getFsVisitorData(visitorData) {
// Start the SDK and get the Flagship instance
const flagship = await startFlagshipSDK();
// Create a visitor
const visitor = flagship.newVisitor({
visitorId: visitorData.visitorId,
context: visitorData.context,
hasConsented: visitorData.hasConsented,
});
// Fetch flag values for the visitor
await visitor.fetchFlags();
// Return visitor instance
return visitor;
}
3. Create a layout component with SSR-aware FlagshipProvider
Set up your root layout component that handles hydration from server to client:
// app/root.tsx
export function Layout({ children }: { children: React.ReactNode }) {
// Get initial flags data and visitor data from the server loader
const data = useRouteLoaderData<typeof loader>("root");
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<FsProvider
initialFlagsData={data?.fsInitialFlagsData} // Server-fetched flags for hydration
visitorData={data?.visitorData} // Visitor data from server
>
<div className={"container"}>
<div className="nav">
<Nav />
</div>
<main className={"main"}>{children}</main>
</div>
<ScrollRestoration />
<Scripts />
</FsProvider>
</body>
</html>
);
}
4. Access flags in server and client components
There are two main ways to access flags in your components:
Option 1: Use the useFsFlag
hook in your page components
// app/routes/anotherPage.tsx
import { useFsFlag } from "@flagship.io/react-sdk";
export default function AnotherPage() {
// Get the flag `btnColor` using useFsFlag hook (client-side)
const flag = useFsFlag("btnColor");
const flagValue = flag.getValue("#dc3545");
return (
<>
<h1>This is another page</h1>
<p>flag key: btnColor</p>
<p>flag value: {flagValue}</p>
<button style={{ background: flagValue }}>Click me</button>
</>
);
}
Option 2: Create dedicated components for flag access
For more complex use cases, you can create dedicated components:
// app/components/MyFlagComponent.tsx
import { useFsFlag } from "@flagship.io/react-sdk";
export function MyFlagComponent() {
const myFlag = useFsFlag("my_flag_key");
return <p>flag value: {myFlag.getValue("default-value")}</p>
}
Use client components in your routes:
// app/routes/home.tsx
import { MyFlagComponent } from "~/components/MyFlagComponent";
import { MyButtonSendHit } from "~/components/MyButtonSendHit";
// This can be a server component that includes client components
export default function Home() {
return (
<>
<h1>Example of Flagship implementation with React Router SSR</h1>
<p>flag key: my_flag_key</p>
{/* Client component that accesses Flagship */}
<MyFlagComponent />
<MyButtonSendHit />
</>
);
}
5. Send analytics hits from client components
To send analytics hits from your client components, use the Flagship SDK:
// app/components/MyButtonSendHit.tsx
import { useFlagship, HitType, EventCategory } from "@flagship.io/react-sdk";
export function MyButtonSendHit() {
const fs = useFlagship();
const onSendHitClick = () => {
fs.sendHits({
type: HitType.EVENT,
category: EventCategory.ACTION_TRACKING,
action: "click button",
});
};
return (
<button
style={{ width: 100, height: 50 }}
onClick={() => {
onSendHitClick();
}}
>
Send hits
</button>
);
}
Server-side considerations for SSR
When using React Router 7+ with SSR, there are several important considerations:
Flag consistency: The flags fetched on the server must match the initial state on the client to prevent flickering.
Visitor identity: Generate or retrieve consistent visitor IDs on the server that persist to the client.
Environment variables: Ensure environment variables are accessible in both server and client contexts.
Performance optimization: Fetch flag data as early as possible in the server request lifecycle.
This approach ensures a smooth transition from server-rendered content to client-side interactivity without any flag flickering or inconsistencies while handling potential errors gracefully.
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
Last updated
Was this helpful?