Skip to content

Commit 9d076e2

Browse files
committed
updated Next.js example app
1 parent 39960a3 commit 9d076e2

File tree

8 files changed

+257
-76
lines changed

8 files changed

+257
-76
lines changed

examples/nextjs/README.md

Lines changed: 77 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# Unleash Toolbar - Next.js Example
22

3-
This example demonstrates how to integrate the Unleash Toolbar with a Next.js App Router application using **built-in React hooks**.
3+
This example demonstrates how to integrate the Unleash Toolbar with a Next.js App Router application for both **client-side and server-side rendering**.
44

55
## Features
66

77
- Next.js 15 with App Router
88
- TypeScript
99
- Tailwind CSS
10-
- Unleash SDK integration with client-side feature flags
11-
- **Built-in hooks** (`useFlag`, `useVariant`) - minimal integration!
10+
- **Client-side integration** with config-based API (no manual client instantiation!)
11+
- **Server-side support** via cookie-based override sync
12+
- Built-in hooks (`useFlag`, `useVariant`) from `@unleash/toolbar/next`
1213
- Override Toolbar for testing flags locally
1314

1415
## Running the Example
@@ -26,45 +27,95 @@ This example demonstrates how to integrate the Unleash Toolbar with a Next.js Ap
2627

2728
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
2829

29-
## How It Works
30+
## Integration Patterns
3031

31-
1. **UnleashProvider** (`components/UnleashProvider.tsx`):
32-
- Creates Unleash client
33-
- Wraps app with `UnleashToolbarProvider` (initializes toolbar automatically)
34-
- That's it - one simple wrapper!
32+
### Client Components
3533

36-
2. **FeatureDemo** (`components/FeatureDemo.tsx`):
37-
- Uses `useFlag('flag-name')` for boolean flags
38-
- Uses `useVariant('flag-name')` for variant flags
39-
- No manual subscriptions or state management needed!
34+
Simple, direct integration - use `UnleashToolbarProvider` directly in your root layout:
4035

41-
3. **Layout** (`app/layout.tsx`): Wraps the app with UnleashProvider
36+
```tsx
37+
// app/layout.tsx
38+
import { UnleashToolbarProvider } from '@unleash/toolbar/next';
39+
import '@unleash/toolbar/toolbar.css';
40+
41+
export default function RootLayout({ children }) {
42+
return (
43+
<html>
44+
<body>
45+
<UnleashToolbarProvider
46+
config={{
47+
url: process.env.NEXT_PUBLIC_UNLEASH_URL,
48+
clientKey: process.env.NEXT_PUBLIC_UNLEASH_CLIENT_KEY,
49+
appName: 'my-app'
50+
}}
51+
toolbarOptions={{ themePreset: 'dark' }}
52+
>
53+
{children}
54+
</UnleashToolbarProvider>
55+
</body>
56+
</html>
57+
);
58+
}
59+
```
4260

43-
## Integration Simplicity
61+
Then use hooks in any client component:
4462

45-
**The entire integration is just 3 lines:**
4663
```tsx
47-
<UnleashToolbarProvider client={client} toolbarOptions={{...}}>
48-
{children}
49-
</UnleashToolbarProvider>
64+
'use client';
65+
import { useFlag, useVariant } from '@unleash/toolbar/next';
66+
67+
export function MyComponent() {
68+
const newCheckout = useFlag('new-checkout');
69+
const paymentVariant = useVariant('payment-provider');
70+
71+
return <div>...</div>;
72+
}
5073
```
5174

52-
**Then use hooks anywhere:**
75+
### Server Components (Requires @unleash/nextjs SDK)
76+
77+
For server-side rendering with toolbar overrides:
78+
5379
```tsx
54-
const newCheckout = useFlag('new-checkout');
55-
const paymentVariant = useVariant('payment-provider');
80+
import { cookies } from 'next/headers';
81+
import { getDefinitions, evaluateFlags, flagsClient } from '@unleash/nextjs';
82+
import { applyToolbarOverrides } from '@unleash/toolbar/next/server';
83+
84+
export default async function ServerPage() {
85+
const cookieStore = await cookies();
86+
const definitions = await getDefinitions();
87+
88+
// Apply toolbar overrides to definitions
89+
const modifiedDefinitions = applyToolbarOverrides(definitions, cookieStore);
90+
91+
const { toggles } = evaluateFlags(modifiedDefinitions, {
92+
sessionId: 'session-123'
93+
});
94+
95+
const flags = flagsClient(toggles);
96+
const isEnabled = flags.isEnabled('my-flag');
97+
98+
return <div>{isEnabled ? 'ON' : 'OFF'}</div>;
99+
}
56100
```
57101

58-
No event subscriptions. No state management. Just hooks that work.
102+
Visit `/server-demo` to see server component documentation.
103+
104+
## How It Works
105+
106+
1. **Client-side**: Toolbar wraps the Unleash client and stores overrides in localStorage + cookies
107+
2. **Server-side**: `applyToolbarOverrides()` reads from cookies and modifies flag definitions before evaluation
108+
3. **Sync**: Client changes sync to cookies automatically, server picks them up on next request
109+
4. **FOUC**: Accept Flash of Unstyled Content (server renders original, client updates) - fine for dev tooling
59110

60111
## Using the Toolbar
61112

62113
1. The toolbar appears at the bottom of the page
63114
2. Click the "Flags" tab to override feature flags
64115
3. Click the "Context" tab to override context fields
65-
4. Changes trigger automatic re-renders
66-
5. Overrides are persisted in localStorage
67-
6. Refresh the page to see that overrides persist
116+
4. Changes trigger automatic re-renders in client components
117+
5. Server components receive overrides on next page load
118+
6. Overrides persist across page reloads via localStorage + cookies
68119

69120
## Feature Flags in Demo
70121

@@ -76,4 +127,4 @@ No event subscriptions. No state management. Just hooks that work.
76127

77128
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
78129

79-
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
130+
**Note**: Disable the toolbar in production by setting `toolbarOptions={undefined}` or using environment checks.

examples/nextjs/app/layout.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Metadata } from "next";
22
import { Geist, Geist_Mono } from "next/font/google";
33
import "./globals.css";
4-
import { UnleashProvider } from "@/components/UnleashProvider";
4+
import { UnleashToolbarProvider } from "@unleash/toolbar/next";
5+
import "@unleash/toolbar/toolbar.css";
56

67
const geistSans = Geist({
78
variable: "--font-geist-sans",
@@ -28,9 +29,22 @@ export default function RootLayout({
2829
<body
2930
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
3031
>
31-
<UnleashProvider>
32+
<UnleashToolbarProvider
33+
config={{
34+
url: process.env.NEXT_PUBLIC_UNLEASH_URL!,
35+
clientKey: process.env.NEXT_PUBLIC_UNLEASH_CLIENT_KEY!,
36+
appName: process.env.NEXT_PUBLIC_UNLEASH_APP_NAME || 'nextjs-demo',
37+
environment: 'development',
38+
refreshInterval: 15,
39+
}}
40+
toolbarOptions={
41+
process.env.NODE_ENV !== 'production'
42+
? { themePreset: 'dark', initiallyVisible: false }
43+
: undefined
44+
}
45+
>
3246
{children}
33-
</UnleashProvider>
47+
</UnleashToolbarProvider>
3448
</body>
3549
</html>
3650
);

examples/nextjs/app/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ export default function Home() {
1717
</div>
1818

1919
<FeatureDemo />
20+
21+
<div className="flex gap-4 mt-4">
22+
<a
23+
href="/server-demo"
24+
className="px-4 py-2 rounded-lg bg-zinc-900 dark:bg-zinc-100 text-white dark:text-black font-medium hover:bg-zinc-700 dark:hover:bg-zinc-300 transition-colors"
25+
>
26+
View Server Component Demo →
27+
</a>
28+
</div>
2029
</main>
2130
</div>
2231
);
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { cookies } from 'next/headers';
2+
import { getToolbarStateFromCookies } from '@unleash/toolbar/next/server';
3+
4+
/**
5+
* Server Component demo for Unleash Toolbar + Next.js
6+
*
7+
* This demonstrates how toolbar overrides are accessible in Next.js Server Components.
8+
* The toolbar syncs client-side overrides to cookies, which server components can read.
9+
*
10+
* To apply overrides to actual flag evaluations with @unleash/nextjs SDK:
11+
*
12+
* import { getDefinitions, evaluateFlags, flagsClient } from '@unleash/nextjs';
13+
* import { applyToolbarOverrides } from '@unleash/toolbar/next/server';
14+
*
15+
* const definitions = await getDefinitions();
16+
* const modified = applyToolbarOverrides(definitions, await cookies());
17+
* const { toggles } = evaluateFlags(modified, { sessionId: '123' });
18+
* const flags = flagsClient(toggles);
19+
*/
20+
export default async function ServerComponentDemo() {
21+
const cookieStore = await cookies();
22+
const toolbarState = getToolbarStateFromCookies(cookieStore);
23+
24+
const hasOverrides = toolbarState && Object.keys(toolbarState.flags).length > 0;
25+
const flagCount = toolbarState ? Object.keys(toolbarState.flags).length : 0;
26+
27+
return (
28+
<div className="flex min-h-screen items-center justify-center bg-zinc-50 dark:bg-zinc-900 p-8">
29+
<main className="flex flex-col items-center gap-8 max-w-3xl">
30+
<div className="text-center space-y-4">
31+
<h1 className="text-4xl font-bold text-black dark:text-white">
32+
Server Component Demo
33+
</h1>
34+
<p className="text-lg text-zinc-600 dark:text-zinc-400">
35+
This Next.js Server Component reads toolbar state from cookies.
36+
</p>
37+
</div>
38+
39+
<div className="w-full space-y-6">
40+
<div className="rounded-lg border border-zinc-200 dark:border-zinc-800 p-6 space-y-4">
41+
<h2 className="text-2xl font-semibold text-black dark:text-white">
42+
Server-Side Toolbar State
43+
</h2>
44+
<div className="flex items-center gap-3">
45+
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
46+
hasOverrides
47+
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
48+
: 'bg-zinc-100 text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200'
49+
}`}>
50+
{hasOverrides ? `${flagCount} Flags${flagCount !== 1 ? 's' : ''}` : 'No Flags'}
51+
</span>
52+
</div>
53+
<p className="text-sm text-zinc-600 dark:text-zinc-400">
54+
{hasOverrides
55+
? 'Server component successfully read toolbar state from cookies!'
56+
: 'No flags detected. Go to the home page and set some flags using the toolbar.'}
57+
</p>
58+
</div>
59+
60+
{hasOverrides && toolbarState && (
61+
<div className="rounded-lg border border-zinc-200 dark:border-zinc-800 p-6 space-y-4">
62+
<h3 className="text-lg font-medium text-black dark:text-white">
63+
Flags Detected
64+
</h3>
65+
<div className="space-y-3">
66+
{Object.entries(toolbarState.flags).map(([flagName, metadata]) => (
67+
<div key={flagName} className="flex items-center justify-between p-3 bg-zinc-50 dark:bg-zinc-800 rounded">
68+
<code className="text-sm font-mono text-black dark:text-white">
69+
{flagName}
70+
</code>
71+
<span className="text-sm text-zinc-600 dark:text-zinc-400">
72+
{metadata.override?.type === 'flag'
73+
? `Override: ${metadata.override.value ? 'ON' : 'OFF'}`
74+
: metadata.override?.type === 'variant'
75+
? `Variant: ${metadata.override.variantKey}`
76+
: 'No override'}
77+
</span>
78+
</div>
79+
))}
80+
</div>
81+
</div>
82+
)}
83+
84+
<div className="rounded-lg border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-950 p-6 space-y-4">
85+
<h3 className="text-xl font-semibold text-blue-900 dark:text-blue-100">
86+
📦 Using with @unleash/nextjs
87+
</h3>
88+
<p className="text-sm text-blue-800 dark:text-blue-200">
89+
To apply these overrides to actual flag evaluations, use applyToolbarOverrides():
90+
</p>
91+
<pre className="bg-blue-100 dark:bg-blue-900 rounded p-3 text-xs text-blue-900 dark:text-blue-100 overflow-x-auto font-mono">
92+
{`import { getDefinitions, evaluateFlags, flagsClient } from '@unleash/nextjs';
93+
import { applyToolbarOverrides } from '@unleash/toolbar/next/server';
94+
95+
const definitions = await getDefinitions();
96+
const modified = applyToolbarOverrides(definitions, await cookies());
97+
const { toggles } = evaluateFlags(modified, context);
98+
const flags = flagsClient(toggles);`}
99+
</pre>
100+
</div>
101+
102+
<div className="rounded-lg border border-zinc-200 dark:border-zinc-800 p-6">
103+
<h3 className="text-lg font-medium text-black dark:text-white mb-3">
104+
How It Works
105+
</h3>
106+
<ol className="list-decimal list-inside space-y-2 text-sm text-zinc-600 dark:text-zinc-400">
107+
<li>Client-side toolbar sets overrides in localStorage</li>
108+
<li>Overrides are automatically synced to cookies (7-day expiration)</li>
109+
<li>Server components read cookies using getToolbarStateFromCookies()</li>
110+
<li>applyToolbarOverrides() modifies flag definitions before evaluation</li>
111+
<li>Server renders with overridden values (accepts FOUC on first load)</li>
112+
</ol>
113+
</div>
114+
115+
<div className="flex gap-4">
116+
<a
117+
href="/"
118+
className="px-4 py-2 rounded-lg bg-zinc-900 dark:bg-zinc-100 text-white dark:text-black font-medium hover:bg-zinc-700 dark:hover:bg-zinc-300 transition-colors"
119+
>
120+
← Back to Client Demo
121+
</a>
122+
</div>
123+
</div>
124+
</main>
125+
</div>
126+
);
127+
}

examples/nextjs/components/FeatureDemo.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use client';
22

3-
import { useFlag, useVariant } from '@unleash/toolbar/react';
3+
import { useFlag, useVariant } from '@unleash/toolbar/next';
44

55
export function FeatureDemo() {
6-
// Use built-in hooks - they automatically handle re-renders on override changes!
6+
// Use hooks from toolbar/next - they automatically handle re-renders on override changes!
77
const newCheckout = useFlag('new-checkout');
88
const darkMode = useFlag('dark-mode');
99
const paymentVariant = useVariant('payment-provider');
@@ -33,7 +33,7 @@ export function FeatureDemo() {
3333
/>
3434
<VariantCard
3535
name="payment-provider"
36-
variant={paymentVariant.enabled ? paymentVariant.name : 'default'}
36+
variant={paymentVariant.enabled ? paymentVariant.name || 'default' : 'default'}
3737
description="Select payment provider (stripe, paypal, square)"
3838
/>
3939
</div>

examples/nextjs/components/UnleashProvider.tsx

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)