@replanejs/next 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -296
- package/dist/index.cjs +100 -174
- package/dist/index.d.cts +23 -183
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +24 -184
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -171
- package/dist/index.js.map +1 -1
- package/package.json +7 -15
- package/dist/chunk-CUT6urMc.cjs +0 -30
- package/dist/server.cjs +0 -95
- package/dist/server.d.cts +0 -114
- package/dist/server.d.cts.map +0 -1
- package/dist/server.d.ts +0 -114
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -94
- package/dist/server.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,424 +2,288 @@
|
|
|
2
2
|
|
|
3
3
|
Next.js SDK for Replane - feature flags and remote configuration with SSR support.
|
|
4
4
|
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **SSR Hydration**: Fetch configs on the server, instantly hydrate on the client
|
|
8
|
-
- **Zero Loading States**: Users see correct feature flags immediately
|
|
9
|
-
- **Real-time Updates**: Optional live connection for instant config changes
|
|
10
|
-
- **Type-safe**: Full TypeScript support with generics
|
|
11
|
-
- **Next.js Optimized**: Works with App Router, Pages Router, and Server Components
|
|
12
|
-
|
|
13
5
|
## Installation
|
|
14
6
|
|
|
15
7
|
```bash
|
|
16
8
|
npm install @replanejs/next
|
|
17
9
|
# or
|
|
18
10
|
pnpm add @replanejs/next
|
|
19
|
-
# or
|
|
20
|
-
yarn add @replanejs/next
|
|
21
11
|
```
|
|
22
12
|
|
|
23
13
|
## Quick Start
|
|
24
14
|
|
|
25
|
-
###
|
|
15
|
+
### App Router (Recommended)
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
# Server-side (not exposed to browser)
|
|
29
|
-
REPLANE_BASE_URL=https://your-replane-instance.com
|
|
30
|
-
REPLANE_SDK_KEY=rp_your_server_sdk_key
|
|
31
|
-
|
|
32
|
-
# Client-side (exposed to browser, for real-time updates)
|
|
33
|
-
NEXT_PUBLIC_REPLANE_BASE_URL=https://your-replane-instance.com
|
|
34
|
-
NEXT_PUBLIC_REPLANE_SDK_KEY=rp_your_client_sdk_key
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### 2. Create the provider in your layout (App Router)
|
|
17
|
+
**1. Set up ReplaneRoot in your layout:**
|
|
38
18
|
|
|
39
19
|
```tsx
|
|
40
20
|
// app/layout.tsx
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
children: React.ReactNode;
|
|
48
|
-
}) {
|
|
49
|
-
const snapshot = await getReplaneSnapshot({
|
|
50
|
-
baseUrl: process.env.REPLANE_BASE_URL!,
|
|
51
|
-
sdkKey: process.env.REPLANE_SDK_KEY!,
|
|
52
|
-
});
|
|
21
|
+
import { ReplaneRoot } from "@replanejs/next";
|
|
22
|
+
|
|
23
|
+
interface AppConfigs {
|
|
24
|
+
theme: { darkMode: boolean; primaryColor: string };
|
|
25
|
+
features: { betaEnabled: boolean };
|
|
26
|
+
}
|
|
53
27
|
|
|
28
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
54
29
|
return (
|
|
55
30
|
<html lang="en">
|
|
56
31
|
<body>
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
connection={{
|
|
32
|
+
<ReplaneRoot<AppConfigs>
|
|
33
|
+
options={{
|
|
60
34
|
baseUrl: process.env.NEXT_PUBLIC_REPLANE_BASE_URL!,
|
|
61
35
|
sdkKey: process.env.NEXT_PUBLIC_REPLANE_SDK_KEY!,
|
|
62
36
|
}}
|
|
63
37
|
>
|
|
64
38
|
{children}
|
|
65
|
-
</
|
|
39
|
+
</ReplaneRoot>
|
|
66
40
|
</body>
|
|
67
41
|
</html>
|
|
68
42
|
);
|
|
69
43
|
}
|
|
70
44
|
```
|
|
71
45
|
|
|
72
|
-
|
|
46
|
+
**2. Use configs in client components:**
|
|
73
47
|
|
|
74
48
|
```tsx
|
|
75
|
-
//
|
|
49
|
+
// components/ThemeToggle.tsx
|
|
76
50
|
"use client";
|
|
77
51
|
|
|
78
52
|
import { useConfig } from "@replanejs/next";
|
|
79
53
|
|
|
80
|
-
export function
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (!isEnabled) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return <div>Feature enabled! Max items: {maxItems}</div>;
|
|
54
|
+
export function ThemeToggle() {
|
|
55
|
+
const theme = useConfig<{ darkMode: boolean }>("theme");
|
|
56
|
+
return <div>{theme.darkMode ? "Dark Mode" : "Light Mode"}</div>;
|
|
89
57
|
}
|
|
90
58
|
```
|
|
91
59
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
### Server Functions
|
|
95
|
-
|
|
96
|
-
#### `getReplaneSnapshot(options)`
|
|
60
|
+
### Pages Router
|
|
97
61
|
|
|
98
|
-
|
|
62
|
+
**1. Set up ReplaneProvider in _app.tsx:**
|
|
99
63
|
|
|
100
64
|
```tsx
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
baseUrl: "https://your-replane-instance.com",
|
|
106
|
-
sdkKey: "rp_your_sdk_key",
|
|
107
|
-
|
|
108
|
-
// Optional
|
|
109
|
-
fetchFn: customFetch, // Custom fetch for caching
|
|
110
|
-
requestTimeoutMs: 2000, // Request timeout (default: 2000)
|
|
111
|
-
initializationTimeoutMs: 5000, // Init timeout (default: 5000)
|
|
112
|
-
context: { userId: "123" }, // Context for override evaluation
|
|
113
|
-
required: ["feature-a", "feature-b"], // Required configs
|
|
114
|
-
fallbacks: { "feature-a": false }, // Fallback values
|
|
115
|
-
});
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
#### `getConfig(options)`
|
|
119
|
-
|
|
120
|
-
Get a single config value directly on the server.
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
import { getConfig } from "@replanejs/next/server";
|
|
65
|
+
// pages/_app.tsx
|
|
66
|
+
import type { AppContext, AppProps } from "next/app";
|
|
67
|
+
import App from "next/app";
|
|
68
|
+
import { ReplaneProvider, getReplaneSnapshot, type ReplaneSnapshot } from "@replanejs/next";
|
|
124
69
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
context: { region: "us-east" },
|
|
130
|
-
});
|
|
70
|
+
interface AppConfigs {
|
|
71
|
+
theme: { darkMode: boolean; primaryColor: string };
|
|
72
|
+
features: { betaEnabled: boolean };
|
|
73
|
+
}
|
|
131
74
|
|
|
132
|
-
|
|
133
|
-
|
|
75
|
+
interface AppPropsWithReplane extends AppProps {
|
|
76
|
+
replaneSnapshot: ReplaneSnapshot<AppConfigs>;
|
|
134
77
|
}
|
|
135
|
-
```
|
|
136
78
|
|
|
137
|
-
|
|
79
|
+
export default function MyApp({ Component, pageProps, replaneSnapshot }: AppPropsWithReplane) {
|
|
80
|
+
return (
|
|
81
|
+
<ReplaneProvider
|
|
82
|
+
snapshot={replaneSnapshot}
|
|
83
|
+
options={{
|
|
84
|
+
baseUrl: process.env.NEXT_PUBLIC_REPLANE_BASE_URL!,
|
|
85
|
+
sdkKey: process.env.NEXT_PUBLIC_REPLANE_SDK_KEY!,
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<Component {...pageProps} />
|
|
89
|
+
</ReplaneProvider>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
138
92
|
|
|
139
|
-
|
|
93
|
+
// Fetch Replane snapshot for all pages
|
|
94
|
+
MyApp.getInitialProps = async (appContext: AppContext) => {
|
|
95
|
+
const appProps = await App.getInitialProps(appContext);
|
|
140
96
|
|
|
141
|
-
|
|
97
|
+
const replaneSnapshot = await getReplaneSnapshot<AppConfigs>({
|
|
98
|
+
baseUrl: process.env.REPLANE_BASE_URL!,
|
|
99
|
+
sdkKey: process.env.REPLANE_SDK_KEY!,
|
|
100
|
+
});
|
|
142
101
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<ReplaneNextProvider
|
|
147
|
-
snapshot={snapshot} // Required: from getReplaneSnapshot()
|
|
148
|
-
connection={{
|
|
149
|
-
// Optional: for real-time updates
|
|
150
|
-
baseUrl: "https://...",
|
|
151
|
-
sdkKey: "rp_...",
|
|
152
|
-
requestTimeoutMs: 2000,
|
|
153
|
-
retryDelayMs: 200,
|
|
154
|
-
inactivityTimeoutMs: 30000,
|
|
155
|
-
}}
|
|
156
|
-
context={{ userId: "123" }} // Optional: override context on client
|
|
157
|
-
>
|
|
158
|
-
{children}
|
|
159
|
-
</ReplaneNextProvider>;
|
|
102
|
+
return { ...appProps, replaneSnapshot };
|
|
103
|
+
};
|
|
160
104
|
```
|
|
161
105
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
Alternative hydration pattern using embedded scripts.
|
|
106
|
+
**2. Use configs in any component:**
|
|
165
107
|
|
|
166
108
|
```tsx
|
|
167
|
-
//
|
|
168
|
-
import {
|
|
169
|
-
|
|
170
|
-
export default async function RootLayout({ children }) {
|
|
171
|
-
const snapshot = await getReplaneSnapshot({ ... });
|
|
109
|
+
// components/FeatureFlag.tsx
|
|
110
|
+
import { useConfig } from "@replanejs/next";
|
|
172
111
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<script
|
|
177
|
-
dangerouslySetInnerHTML={{
|
|
178
|
-
__html: getReplaneSnapshotScript(snapshot),
|
|
179
|
-
}}
|
|
180
|
-
/>
|
|
181
|
-
</head>
|
|
182
|
-
<body>
|
|
183
|
-
<ReplaneScriptProvider connection={{ baseUrl, sdkKey }}>
|
|
184
|
-
{children}
|
|
185
|
-
</ReplaneScriptProvider>
|
|
186
|
-
</body>
|
|
187
|
-
</html>
|
|
188
|
-
);
|
|
112
|
+
export function FeatureFlag() {
|
|
113
|
+
const features = useConfig<{ betaEnabled: boolean }>("features");
|
|
114
|
+
return features.betaEnabled ? <BetaFeature /> : null;
|
|
189
115
|
}
|
|
190
116
|
```
|
|
191
117
|
|
|
192
|
-
|
|
118
|
+
## Typed Hooks (Recommended)
|
|
193
119
|
|
|
194
|
-
|
|
120
|
+
For better type safety and autocomplete, create typed hooks for your application:
|
|
195
121
|
|
|
196
|
-
|
|
122
|
+
**1. Define your config types:**
|
|
197
123
|
|
|
198
|
-
```
|
|
199
|
-
|
|
124
|
+
```ts
|
|
125
|
+
// replane/types.ts
|
|
126
|
+
export interface AppConfigs {
|
|
127
|
+
theme: {
|
|
128
|
+
darkMode: boolean;
|
|
129
|
+
primaryColor: string;
|
|
130
|
+
};
|
|
131
|
+
features: {
|
|
132
|
+
betaEnabled: boolean;
|
|
133
|
+
maxItems: number;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
```
|
|
200
137
|
|
|
201
|
-
|
|
202
|
-
// Basic usage
|
|
203
|
-
const feature = useConfig<boolean>("feature-flag");
|
|
138
|
+
**2. Create typed hooks:**
|
|
204
139
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
140
|
+
```ts
|
|
141
|
+
// replane/hooks.ts
|
|
142
|
+
import { createConfigHook, createReplaneHook } from "@replanejs/next";
|
|
143
|
+
import type { AppConfigs } from "./types";
|
|
209
144
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
```
|
|
145
|
+
// Typed hook for accessing individual configs
|
|
146
|
+
export const useAppConfig = createConfigHook<AppConfigs>();
|
|
213
147
|
|
|
214
|
-
|
|
148
|
+
// Typed hook for accessing the Replane client
|
|
149
|
+
export const useAppReplane = createReplaneHook<AppConfigs>();
|
|
150
|
+
```
|
|
215
151
|
|
|
216
|
-
|
|
152
|
+
**3. Use in components:**
|
|
217
153
|
|
|
218
154
|
```tsx
|
|
219
|
-
|
|
155
|
+
// components/ConfigDisplay.tsx
|
|
156
|
+
"use client";
|
|
220
157
|
|
|
221
|
-
|
|
222
|
-
const { client } = useReplane();
|
|
158
|
+
import { useAppConfig, useAppReplane } from "@/replane/hooks";
|
|
223
159
|
|
|
224
|
-
|
|
225
|
-
|
|
160
|
+
export function ConfigDisplay() {
|
|
161
|
+
// Config names autocomplete, values are fully typed
|
|
162
|
+
const theme = useAppConfig("theme");
|
|
163
|
+
// theme.darkMode is boolean, theme.primaryColor is string
|
|
226
164
|
|
|
227
|
-
|
|
165
|
+
// Or use the client directly for more control
|
|
166
|
+
const replane = useAppReplane();
|
|
167
|
+
const snapshot = replane.getSnapshot();
|
|
168
|
+
|
|
169
|
+
return <div style={{ color: theme.primaryColor }}>...</div>;
|
|
228
170
|
}
|
|
229
171
|
```
|
|
230
172
|
|
|
231
|
-
##
|
|
173
|
+
## API Reference
|
|
232
174
|
|
|
233
|
-
###
|
|
175
|
+
### Components
|
|
234
176
|
|
|
235
|
-
|
|
177
|
+
#### `ReplaneRoot`
|
|
236
178
|
|
|
237
|
-
|
|
238
|
-
// ISR: Revalidate every 60 seconds
|
|
239
|
-
const snapshot = await getReplaneSnapshot({
|
|
240
|
-
baseUrl: process.env.REPLANE_BASE_URL!,
|
|
241
|
-
sdkKey: process.env.REPLANE_SDK_KEY!,
|
|
242
|
-
fetchFn: (url, init) =>
|
|
243
|
-
fetch(url, {
|
|
244
|
-
...init,
|
|
245
|
-
next: { revalidate: 60 },
|
|
246
|
-
}),
|
|
247
|
-
});
|
|
248
|
-
```
|
|
179
|
+
Server component for App Router that fetches configs and provides them to the app.
|
|
249
180
|
|
|
250
181
|
```tsx
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// In a server action or route handler:
|
|
263
|
-
// revalidateTag('replane-config');
|
|
182
|
+
<ReplaneRoot<AppConfigs>
|
|
183
|
+
options={{
|
|
184
|
+
baseUrl: string;
|
|
185
|
+
sdkKey: string;
|
|
186
|
+
// ... other ReplaneClientOptions
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
{children}
|
|
190
|
+
</ReplaneRoot>
|
|
264
191
|
```
|
|
265
192
|
|
|
266
|
-
|
|
193
|
+
#### `ReplaneProvider`
|
|
267
194
|
|
|
268
|
-
|
|
195
|
+
Client-side provider for Pages Router or custom setups.
|
|
269
196
|
|
|
270
197
|
```tsx
|
|
271
|
-
<
|
|
272
|
-
{
|
|
198
|
+
<ReplaneProvider
|
|
199
|
+
snapshot={replaneSnapshot}
|
|
200
|
+
options={{
|
|
201
|
+
baseUrl: string;
|
|
202
|
+
sdkKey: string;
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
273
205
|
{children}
|
|
274
|
-
</
|
|
206
|
+
</ReplaneProvider>
|
|
275
207
|
```
|
|
276
208
|
|
|
277
|
-
###
|
|
209
|
+
### Hooks
|
|
278
210
|
|
|
279
|
-
|
|
280
|
-
// pages/_app.tsx
|
|
281
|
-
import { ReplaneNextProvider } from "@replanejs/next";
|
|
282
|
-
import type { AppProps } from "next/app";
|
|
211
|
+
#### `useConfig<T>(name: string): T`
|
|
283
212
|
|
|
284
|
-
|
|
285
|
-
return (
|
|
286
|
-
<ReplaneNextProvider
|
|
287
|
-
snapshot={pageProps.replaneSnapshot}
|
|
288
|
-
connection={{
|
|
289
|
-
baseUrl: process.env.NEXT_PUBLIC_REPLANE_BASE_URL!,
|
|
290
|
-
sdkKey: process.env.NEXT_PUBLIC_REPLANE_SDK_KEY!,
|
|
291
|
-
}}
|
|
292
|
-
>
|
|
293
|
-
<Component {...pageProps} />
|
|
294
|
-
</ReplaneNextProvider>
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
```
|
|
213
|
+
Returns the value of a config by name. Re-renders when the config changes.
|
|
298
214
|
|
|
299
215
|
```tsx
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
import type { GetServerSideProps } from "next";
|
|
216
|
+
const theme = useConfig<{ darkMode: boolean }>("theme");
|
|
217
|
+
```
|
|
303
218
|
|
|
304
|
-
|
|
305
|
-
const replaneSnapshot = await getReplaneSnapshot({
|
|
306
|
-
baseUrl: process.env.REPLANE_BASE_URL!,
|
|
307
|
-
sdkKey: process.env.REPLANE_SDK_KEY!,
|
|
308
|
-
});
|
|
219
|
+
#### `useReplane<T>(): ReplaneClient<T>`
|
|
309
220
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
221
|
+
Returns the Replane client instance for advanced usage.
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
const client = useReplane<AppConfigs>();
|
|
225
|
+
const snapshot = client.getSnapshot();
|
|
226
|
+
const theme = client.get("theme");
|
|
314
227
|
```
|
|
315
228
|
|
|
316
|
-
|
|
229
|
+
#### `createConfigHook<T>()`
|
|
317
230
|
|
|
318
|
-
|
|
231
|
+
Creates a typed version of `useConfig` for your config schema.
|
|
319
232
|
|
|
320
233
|
```tsx
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
baseUrl: process.env.REPLANE_BASE_URL!,
|
|
324
|
-
sdkKey: process.env.REPLANE_SDK_KEY!,
|
|
325
|
-
context: {
|
|
326
|
-
userId: user.id,
|
|
327
|
-
plan: user.subscription,
|
|
328
|
-
country: user.country,
|
|
329
|
-
},
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// Client-side: override or extend context
|
|
333
|
-
<ReplaneNextProvider
|
|
334
|
-
snapshot={snapshot}
|
|
335
|
-
context={{
|
|
336
|
-
// Add client-specific context
|
|
337
|
-
browser: navigator.userAgent,
|
|
338
|
-
screenSize: window.innerWidth > 768 ? "desktop" : "mobile",
|
|
339
|
-
}}
|
|
340
|
-
>
|
|
341
|
-
{children}
|
|
342
|
-
</ReplaneNextProvider>
|
|
234
|
+
const useAppConfig = createConfigHook<AppConfigs>();
|
|
235
|
+
const theme = useAppConfig("theme"); // fully typed
|
|
343
236
|
```
|
|
344
237
|
|
|
345
|
-
|
|
238
|
+
#### `createReplaneHook<T>()`
|
|
346
239
|
|
|
347
|
-
|
|
240
|
+
Creates a typed version of `useReplane` for your config schema.
|
|
348
241
|
|
|
349
242
|
```tsx
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
sdkKey: process.env.REPLANE_SDK_KEY!,
|
|
353
|
-
required: ["critical-feature", "api-endpoint"],
|
|
354
|
-
// Or with default values:
|
|
355
|
-
required: {
|
|
356
|
-
"critical-feature": true,
|
|
357
|
-
"api-endpoint": "https://default.api.com",
|
|
358
|
-
},
|
|
359
|
-
});
|
|
243
|
+
const useAppReplane = createReplaneHook<AppConfigs>();
|
|
244
|
+
const client = useAppReplane(); // client.get("theme") is typed
|
|
360
245
|
```
|
|
361
246
|
|
|
362
|
-
###
|
|
247
|
+
### Functions
|
|
363
248
|
|
|
364
|
-
|
|
249
|
+
#### `getReplaneSnapshot<T>(options): Promise<ReplaneSnapshot<T>>`
|
|
250
|
+
|
|
251
|
+
Fetches a snapshot of all configs. Use in `getServerSideProps`, `getStaticProps`, or `getInitialProps`.
|
|
365
252
|
|
|
366
253
|
```tsx
|
|
367
|
-
const snapshot = await getReplaneSnapshot({
|
|
254
|
+
const snapshot = await getReplaneSnapshot<AppConfigs>({
|
|
368
255
|
baseUrl: process.env.REPLANE_BASE_URL!,
|
|
369
256
|
sdkKey: process.env.REPLANE_SDK_KEY!,
|
|
370
|
-
|
|
371
|
-
"feature-flag": false,
|
|
372
|
-
"max-items": 10,
|
|
373
|
-
"api-endpoint": "https://fallback.api.com",
|
|
374
|
-
},
|
|
257
|
+
cacheTtlMs: 60_000, // optional, default 60 seconds
|
|
375
258
|
});
|
|
376
259
|
```
|
|
377
260
|
|
|
378
|
-
|
|
261
|
+
#### `clearSnapshotCache(): Promise<void>`
|
|
379
262
|
|
|
380
|
-
|
|
263
|
+
Clears the internal client cache. Useful for testing.
|
|
381
264
|
|
|
382
265
|
```tsx
|
|
383
|
-
|
|
384
|
-
export interface ReplaneConfigs {
|
|
385
|
-
"feature-flag": boolean;
|
|
386
|
-
"max-items": number;
|
|
387
|
-
"api-endpoint": string;
|
|
388
|
-
theme: {
|
|
389
|
-
primaryColor: string;
|
|
390
|
-
darkMode: boolean;
|
|
391
|
-
};
|
|
392
|
-
}
|
|
266
|
+
await clearSnapshotCache();
|
|
393
267
|
```
|
|
394
268
|
|
|
395
|
-
|
|
396
|
-
// Use with generics
|
|
397
|
-
import type { ReplaneConfigs } from "./types/replane";
|
|
269
|
+
## Environment Variables
|
|
398
270
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
271
|
+
```env
|
|
272
|
+
# Server-side only (for SSR/SSG)
|
|
273
|
+
REPLANE_BASE_URL=https://api.replane.io
|
|
274
|
+
REPLANE_SDK_KEY=your-sdk-key
|
|
403
275
|
|
|
404
|
-
|
|
405
|
-
|
|
276
|
+
# Client-side (for live updates)
|
|
277
|
+
NEXT_PUBLIC_REPLANE_BASE_URL=https://api.replane.io
|
|
278
|
+
NEXT_PUBLIC_REPLANE_SDK_KEY=your-sdk-key
|
|
406
279
|
```
|
|
407
280
|
|
|
408
|
-
##
|
|
409
|
-
|
|
410
|
-
The snapshot pattern minimizes latency by:
|
|
411
|
-
|
|
412
|
-
1. **Server Fetch**: Configs are fetched during SSR (no client-side request delay)
|
|
413
|
-
2. **Instant Hydration**: Client instantly has all config values (no loading states)
|
|
414
|
-
3. **Optional Live Updates**: Real-time connection established after hydration
|
|
415
|
-
|
|
416
|
-
This means users see correct feature flags immediately without any loading states or flashes of incorrect content.
|
|
281
|
+
## Examples
|
|
417
282
|
|
|
418
|
-
|
|
283
|
+
See the [examples](./examples) directory for complete working examples:
|
|
419
284
|
|
|
420
|
-
-
|
|
421
|
-
-
|
|
422
|
-
- Node.js >= 18.0.0
|
|
285
|
+
- **[next-app-router](./examples/next-app-router)** - App Router with ReplaneRoot
|
|
286
|
+
- **[next-pages-router](./examples/next-pages-router)** - Pages Router with getInitialProps
|
|
423
287
|
|
|
424
288
|
## License
|
|
425
289
|
|