@splitlabio/nextjs-orchestrator 0.2.1
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/LICENSE +8 -0
- package/README.md +286 -0
- package/dist/MVTOrchestrator.d.ts +34 -0
- package/dist/MVTOrchestrator.js +81 -0
- package/dist/MVTScripts.d.ts +47 -0
- package/dist/MVTScripts.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Splitlab.io License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Splitlab.io. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is proprietary and confidential to Splitlab.io.
|
|
6
|
+
Unauthorized copying, modification, distribution, or use of this software,
|
|
7
|
+
in whole or in part, is strictly prohibited without the prior written
|
|
8
|
+
consent of Splitlab.io.
|
package/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# @splitlabio/nextjs-orchestrator
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@splitlabio/nextjs-orchestrator)
|
|
4
|
+
[](https://www.npmjs.com/package/@splitlabio/nextjs-orchestrator)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
React SDK for integrating the Splitlab CDN engine into Next.js applications with anti-flicker support.
|
|
8
|
+
|
|
9
|
+
> **Renamed:** this package was previously published as `@mvtlab/nextjs-orchestrator`.
|
|
10
|
+
> It is now `@splitlabio/nextjs-orchestrator`; the old package is deprecated and points here.
|
|
11
|
+
> The public API is unchanged — component names (`MVTScripts`, `MVTOrchestrator`), the
|
|
12
|
+
> `orchestratorKey` prop, and the `data-mvt-*` engine attributes are kept as-is because the
|
|
13
|
+
> deployed CDN engine matches on them.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @splitlabio/nextjs-orchestrator
|
|
21
|
+
# or
|
|
22
|
+
yarn add @splitlabio/nextjs-orchestrator
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Requirements:** Next.js >= 13.0.0, React >= 18.0.0
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Recommended — App Router (SSR-safe, no flicker)
|
|
32
|
+
|
|
33
|
+
Use `MVTScripts` in your root `layout.tsx`. It is a **Server Component** — no `'use client'`
|
|
34
|
+
needed — so the anti-flicker style and engine script are injected into the server-rendered
|
|
35
|
+
HTML and execute before React hydration, preventing any content flash.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// app/layout.tsx
|
|
39
|
+
import { MVTScripts } from '@splitlabio/nextjs-orchestrator';
|
|
40
|
+
|
|
41
|
+
export default function RootLayout({ children }) {
|
|
42
|
+
return (
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<body>
|
|
45
|
+
<MVTScripts orchestratorKey="YOUR_PROJECT_KEY" />
|
|
46
|
+
{children}
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
That's it. No `'use client'` required.
|
|
54
|
+
|
|
55
|
+
### Pages Router
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// pages/_app.tsx
|
|
59
|
+
import { MVTOrchestrator } from '@splitlabio/nextjs-orchestrator';
|
|
60
|
+
|
|
61
|
+
export default function MyApp({ Component, pageProps }) {
|
|
62
|
+
return (
|
|
63
|
+
<MVTOrchestrator orchestratorKey="YOUR_PROJECT_KEY">
|
|
64
|
+
<Component {...pageProps} />
|
|
65
|
+
</MVTOrchestrator>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Components
|
|
73
|
+
|
|
74
|
+
### `MVTScripts` — Server Component ✅ Recommended
|
|
75
|
+
|
|
76
|
+
Place once at the top of `<body>` in your root `layout.tsx`.
|
|
77
|
+
|
|
78
|
+
Renders two script tags directly into the server HTML:
|
|
79
|
+
|
|
80
|
+
1. An **inline anti-flicker script** that sets `body { opacity: 0 }` and exposes
|
|
81
|
+
`window.rmfk()` — runs synchronously before any page content is visible.
|
|
82
|
+
2. The **Splitlab engine `<script async>`** that loads the CDN, applies variant changes,
|
|
83
|
+
then calls `window.rmfk()` to reveal the page.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { MVTScripts } from '@splitlabio/nextjs-orchestrator';
|
|
87
|
+
|
|
88
|
+
<MVTScripts
|
|
89
|
+
orchestratorKey="YOUR_PROJECT_KEY"
|
|
90
|
+
antiFlickerEnabled={true} // default
|
|
91
|
+
antiFlickerTimeoutMs={3000} // default
|
|
92
|
+
/>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Prop | Type | Default | Description |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| `orchestratorKey` | `string` | — | Splitlab project key |
|
|
98
|
+
| `antiFlickerEnabled` | `boolean` | `true` | Inject `body{opacity:0}` before hydration |
|
|
99
|
+
| `antiFlickerTimeoutMs` | `number` | `3000` | Fallback timeout to reveal page if engine stalls |
|
|
100
|
+
| `engineUrl` | `string` | `https://cdn.splitlab.io/cdn/engine.js` | Override engine script URL (e.g. point at `https://staging-svc.splitlab.io/scripts/engine.js` for staging) |
|
|
101
|
+
|
|
102
|
+
### `MVTOrchestrator` — Client Component
|
|
103
|
+
|
|
104
|
+
A `'use client'` component that handles script injection client-side and enforces a single
|
|
105
|
+
project key per page. Use this for **Pages Router** apps or when you cannot modify `layout.tsx`.
|
|
106
|
+
|
|
107
|
+
> **Note:** When used without `MVTScripts` in layout, the anti-flicker fires after React
|
|
108
|
+
> hydration and will not prevent the initial SSR content flash. For full flicker prevention
|
|
109
|
+
> use `MVTScripts` in `layout.tsx`.
|
|
110
|
+
>
|
|
111
|
+
> When used **together with `MVTScripts`**, `MVTOrchestrator` automatically detects the
|
|
112
|
+
> server-injected script and skips re-injection — no duplicate scripts.
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { MVTOrchestrator } from '@splitlabio/nextjs-orchestrator';
|
|
116
|
+
|
|
117
|
+
<MVTOrchestrator
|
|
118
|
+
orchestratorKey="YOUR_PROJECT_KEY"
|
|
119
|
+
antiFlickerEnabled={true} // default
|
|
120
|
+
antiFlickerTimeoutMs={3000} // default
|
|
121
|
+
>
|
|
122
|
+
{children}
|
|
123
|
+
</MVTOrchestrator>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Prop | Type | Default | Description |
|
|
127
|
+
| --- | --- | --- | --- |
|
|
128
|
+
| `orchestratorKey` | `string` | — | Splitlab project key |
|
|
129
|
+
| `children` | `ReactNode` | — | App content to wrap |
|
|
130
|
+
| `antiFlickerEnabled` | `boolean` | `true` | Enable anti-flicker |
|
|
131
|
+
| `antiFlickerTimeoutMs` | `number` | `3000` | Fallback timeout |
|
|
132
|
+
| `engineUrl` | `string` | `https://cdn.splitlab.io/cdn/engine.js` | Override engine script URL (e.g. point at `https://staging-svc.splitlab.io/scripts/engine.js` for staging) |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Usage Examples
|
|
137
|
+
|
|
138
|
+
### With environment variable
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
// .env.local
|
|
142
|
+
NEXT_PUBLIC_SPLITLAB_PROJECT_KEY=your_key_here
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<MVTScripts orchestratorKey={process.env.NEXT_PUBLIC_SPLITLAB_PROJECT_KEY!} />
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Point at staging
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<MVTScripts
|
|
153
|
+
orchestratorKey="abc123xyz"
|
|
154
|
+
engineUrl="https://staging-svc.splitlab.io/scripts/engine.js"
|
|
155
|
+
/>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Disable anti-flicker
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<MVTScripts orchestratorKey="abc123xyz" antiFlickerEnabled={false} />
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Custom timeout
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<MVTScripts orchestratorKey="abc123xyz" antiFlickerTimeoutMs={5000} />
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### TypeScript
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import type { MVTScriptsProps, MVTOrchestratorProps } from '@splitlabio/nextjs-orchestrator';
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## How It Works
|
|
179
|
+
|
|
180
|
+
### `MVTScripts` (Server Component)
|
|
181
|
+
|
|
182
|
+
Server-renders two `<script>` tags into the HTML response:
|
|
183
|
+
|
|
184
|
+
```html
|
|
185
|
+
<!-- Anti-flicker: runs synchronously before any content is shown -->
|
|
186
|
+
<script id="mvt-anti-flicker">
|
|
187
|
+
var timeout=3000;!(function(h,i,d,e){ ... body{opacity:0} ... window.rmfk ... })(...)
|
|
188
|
+
</script>
|
|
189
|
+
|
|
190
|
+
<!-- Engine: async, loads CDN, applies variants, calls window.rmfk() -->
|
|
191
|
+
<script
|
|
192
|
+
id="mvt-engine-script"
|
|
193
|
+
src="https://cdn.splitlab.io/cdn/engine.js"
|
|
194
|
+
data-project-key="YOUR_KEY"
|
|
195
|
+
data-mvt="engine"
|
|
196
|
+
data-mvt-engine="true"
|
|
197
|
+
data-flicker-class="abtest-hidden"
|
|
198
|
+
crossorigin="anonymous"
|
|
199
|
+
async
|
|
200
|
+
></script>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Sequence: **body hidden → engine loads → variants applied → body revealed**
|
|
204
|
+
|
|
205
|
+
### `MVTOrchestrator` (Client Component)
|
|
206
|
+
|
|
207
|
+
Injects the same scripts via `useEffect` (after hydration). Detects if `MVTScripts` already
|
|
208
|
+
ran (`id="mvt-engine-script"` present) and skips re-injection.
|
|
209
|
+
|
|
210
|
+
Sequence when standalone: **SSR renders → hydration → useEffect → scripts injected**
|
|
211
|
+
|
|
212
|
+
### Anti-Flicker Detail
|
|
213
|
+
|
|
214
|
+
- `<style id="abhide">body{opacity:0}</style>` is injected into `<head>`
|
|
215
|
+
- `window.rmfk()` is defined to remove that style element
|
|
216
|
+
- The engine calls `window.rmfk()` once variants are applied
|
|
217
|
+
- A safety timeout removes the style if the engine never responds
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Troubleshooting
|
|
222
|
+
|
|
223
|
+
**Engine not loading?**
|
|
224
|
+
|
|
225
|
+
- Verify `orchestratorKey` is the correct project key from your Splitlab dashboard
|
|
226
|
+
- Check the Network tab for the `engine.js` request — it should return **200 from `cdn.splitlab.io`**.
|
|
227
|
+
A failed request to `staging-svc.mvtlab.io` means you are on an old release (< 0.2.1); upgrade or set `engineUrl`.
|
|
228
|
+
- Ensure the project's domain matches the `websiteUrl` configured in Splitlab
|
|
229
|
+
|
|
230
|
+
**Anti-flicker not working (page flashes)?**
|
|
231
|
+
|
|
232
|
+
- Switch from `MVTOrchestrator` to `MVTScripts` in `layout.tsx` — the client-side approach cannot prevent the initial SSR flash
|
|
233
|
+
- Confirm `antiFlickerEnabled={true}` (default)
|
|
234
|
+
|
|
235
|
+
**Duplicate scripts in DOM?**
|
|
236
|
+
|
|
237
|
+
- Use `MVTScripts` or `MVTOrchestrator` once at root, not both independently
|
|
238
|
+
- If using both together, `MVTOrchestrator` auto-detects `MVTScripts` and skips injection
|
|
239
|
+
|
|
240
|
+
**Pages Router / no layout.tsx?**
|
|
241
|
+
|
|
242
|
+
- Use `MVTOrchestrator` in `pages/_app.tsx`
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## FAQ
|
|
247
|
+
|
|
248
|
+
**Do I need `'use client'` with `MVTScripts`?**
|
|
249
|
+
No. `MVTScripts` is a Server Component. Add it directly to your `layout.tsx` without any directive.
|
|
250
|
+
|
|
251
|
+
**Can I use `MVTScripts` and `MVTOrchestrator` together?**
|
|
252
|
+
Yes. Put `MVTScripts` in `layout.tsx` for correct timing and use `MVTOrchestrator` elsewhere for
|
|
253
|
+
client-side key enforcement. The orchestrator detects the server-injected script and won't duplicate it.
|
|
254
|
+
|
|
255
|
+
**Can I use multiple project keys?**
|
|
256
|
+
No. Use one `orchestratorKey` for the entire site. Different keys on the same page will log an error.
|
|
257
|
+
|
|
258
|
+
**What if the engine fails to load?**
|
|
259
|
+
The timeout (default 3000ms) removes the anti-flicker style so the page becomes visible.
|
|
260
|
+
Experiment variants won't be applied.
|
|
261
|
+
|
|
262
|
+
**TypeScript support?**
|
|
263
|
+
Full type definitions included. Both `MVTScriptsProps` and `MVTOrchestratorProps` are exported.
|
|
264
|
+
|
|
265
|
+
**Browser support?**
|
|
266
|
+
Modern browsers with ES2019+, React 18+, and standard DOM APIs.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## License
|
|
271
|
+
|
|
272
|
+
Licensed under Splitlab.io. Copyright © Splitlab.io. All rights reserved.
|
|
273
|
+
|
|
274
|
+
See [LICENSE](LICENSE) for details.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Support
|
|
279
|
+
|
|
280
|
+
- **Splitlab:** Contact your representative or check your Splitlab dashboard.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
*`MVTScripts` renders the standard Splitlab HTML snippet server-side so it runs before
|
|
285
|
+
hydration. `MVTOrchestrator` translates the same snippet into React hooks for client-side
|
|
286
|
+
or Pages Router use.*
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
export type MVTOrchestratorProps = {
|
|
3
|
+
/**
|
|
4
|
+
* Project key used by the MVT engine.
|
|
5
|
+
* This will be passed as data-project-key to the engine script.
|
|
6
|
+
*/
|
|
7
|
+
orchestratorKey: string;
|
|
8
|
+
/**
|
|
9
|
+
* Enable or disable the anti-flicker style injection.
|
|
10
|
+
* When enabled and MVTScripts is NOT present in layout.tsx, a temporary
|
|
11
|
+
* style is applied client-side to hide the body until the engine removes it.
|
|
12
|
+
* Note: when used standalone (without MVTScripts), anti-flicker fires after
|
|
13
|
+
* React hydration and will not prevent the initial SSR content flash.
|
|
14
|
+
* For full flicker prevention use MVTScripts in your root layout.tsx instead.
|
|
15
|
+
* Defaults to true.
|
|
16
|
+
*/
|
|
17
|
+
antiFlickerEnabled?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Timeout in milliseconds to automatically remove the anti-flicker style.
|
|
20
|
+
* Defaults to 3000ms.
|
|
21
|
+
*/
|
|
22
|
+
antiFlickerTimeoutMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Custom engine script URL.
|
|
25
|
+
* Defaults to the live production CDN endpoint. Point this at
|
|
26
|
+
* https://staging-svc.splitlab.io/scripts/engine.js for staging.
|
|
27
|
+
*/
|
|
28
|
+
engineUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Children that will be rendered inside the orchestrator.
|
|
31
|
+
*/
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
};
|
|
34
|
+
export declare const MVTOrchestrator: React.FC<MVTOrchestratorProps>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
const DEFAULT_ENGINE_URL = 'https://cdn.splitlab.io/cdn/engine.js';
|
|
5
|
+
const ANTI_FLICKER_STYLE_ID = 'abhide';
|
|
6
|
+
export const MVTOrchestrator = ({ orchestratorKey, antiFlickerEnabled = true, antiFlickerTimeoutMs = 3000, engineUrl = DEFAULT_ENGINE_URL, children, }) => {
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// Cancel the pending safety timeout on unmount or before the effect re-runs
|
|
12
|
+
// (e.g. antiFlickerTimeoutMs changed), so a stale timer can't fire after
|
|
13
|
+
// unmount or let an earlier timeout win over a newly intended one.
|
|
14
|
+
let safetyTimeoutId;
|
|
15
|
+
const cleanup = () => {
|
|
16
|
+
if (safetyTimeoutId !== undefined) {
|
|
17
|
+
window.clearTimeout(safetyTimeoutId);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
// Detect whether MVTScripts server component already injected the engine.
|
|
21
|
+
// If the <script id="mvt-engine-script"> is present, the server-side path
|
|
22
|
+
// ran (correct timing). We skip script injection but still ensure window.rmfk
|
|
23
|
+
// is available for the engine to call and set the timeout fallback.
|
|
24
|
+
const engineAlreadyInjected = !!document.getElementById('mvt-engine-script');
|
|
25
|
+
// Anti-flicker: only inject client-side when MVTScripts was NOT used.
|
|
26
|
+
// When MVTScripts is in layout.tsx the anti-flicker script is server-rendered
|
|
27
|
+
// and window.rmfk is already defined — we just need to set the timeout fallback.
|
|
28
|
+
if (antiFlickerEnabled) {
|
|
29
|
+
if (!engineAlreadyInjected) {
|
|
30
|
+
// Standalone mode: inject anti-flicker client-side (fires after hydration —
|
|
31
|
+
// does not prevent the initial SSR flash, but still protects against variant
|
|
32
|
+
// flicker on client-side navigations).
|
|
33
|
+
const existing = document.getElementById(ANTI_FLICKER_STYLE_ID);
|
|
34
|
+
if (!existing) {
|
|
35
|
+
const style = document.createElement('style');
|
|
36
|
+
style.id = ANTI_FLICKER_STYLE_ID;
|
|
37
|
+
style.innerHTML = 'body{opacity:0}';
|
|
38
|
+
document.head.appendChild(style);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Expose window.rmfk so the engine can remove the anti-flicker style.
|
|
42
|
+
// MVTScripts also defines this via the inline script; redefining here is safe.
|
|
43
|
+
window.rmfk = function rmfk() {
|
|
44
|
+
const el = document.getElementById(ANTI_FLICKER_STYLE_ID);
|
|
45
|
+
if (el && el.parentNode) {
|
|
46
|
+
el.parentNode.removeChild(el);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
// Safety timeout: remove anti-flicker if the engine never calls rmfk.
|
|
50
|
+
safetyTimeoutId = window.setTimeout(() => {
|
|
51
|
+
const rmfk = window.rmfk;
|
|
52
|
+
if (typeof rmfk === 'function') {
|
|
53
|
+
rmfk();
|
|
54
|
+
}
|
|
55
|
+
}, antiFlickerTimeoutMs);
|
|
56
|
+
}
|
|
57
|
+
const w = window;
|
|
58
|
+
if (w.__mvtOrchestratorKey && w.__mvtOrchestratorKey !== orchestratorKey) {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.error('[MVTOrchestrator] Multiple different project keys on the same page are not supported. ' +
|
|
61
|
+
`Existing key: "${w.__mvtOrchestratorKey}", new key: "${orchestratorKey}".`);
|
|
62
|
+
return cleanup;
|
|
63
|
+
}
|
|
64
|
+
w.__mvtOrchestratorKey = orchestratorKey;
|
|
65
|
+
// Inject the engine script only when MVTScripts did not already do so.
|
|
66
|
+
if (!engineAlreadyInjected) {
|
|
67
|
+
const script = document.createElement('script');
|
|
68
|
+
script.id = 'mvt-engine-script';
|
|
69
|
+
script.src = engineUrl;
|
|
70
|
+
script.async = true;
|
|
71
|
+
script.crossOrigin = 'anonymous';
|
|
72
|
+
script.setAttribute('data-project-key', orchestratorKey);
|
|
73
|
+
script.setAttribute('data-mvt', 'engine');
|
|
74
|
+
script.setAttribute('data-mvt-engine', 'true');
|
|
75
|
+
script.setAttribute('data-flicker-class', 'abtest-hidden');
|
|
76
|
+
document.head.appendChild(script);
|
|
77
|
+
}
|
|
78
|
+
return cleanup;
|
|
79
|
+
}, [orchestratorKey, antiFlickerEnabled, antiFlickerTimeoutMs, engineUrl]);
|
|
80
|
+
return _jsx(_Fragment, { children: children });
|
|
81
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type MVTScriptsProps = {
|
|
3
|
+
/**
|
|
4
|
+
* Project key used by the MVT engine.
|
|
5
|
+
*/
|
|
6
|
+
orchestratorKey: string;
|
|
7
|
+
/**
|
|
8
|
+
* Enable or disable the anti-flicker style injection.
|
|
9
|
+
* Defaults to true.
|
|
10
|
+
*/
|
|
11
|
+
antiFlickerEnabled?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Timeout in milliseconds to automatically remove the anti-flicker style
|
|
14
|
+
* if the engine never calls window.rmfk(). Defaults to 3000ms.
|
|
15
|
+
*/
|
|
16
|
+
antiFlickerTimeoutMs?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Custom engine script URL.
|
|
19
|
+
* Defaults to the live production CDN endpoint. Point this at
|
|
20
|
+
* https://staging-svc.splitlab.io/scripts/engine.js for staging.
|
|
21
|
+
*/
|
|
22
|
+
engineUrl?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Server Component — place at the top of your root layout.tsx <body>.
|
|
26
|
+
*
|
|
27
|
+
* Renders the anti-flicker inline script and the MVT engine <script> tag
|
|
28
|
+
* directly into the server-generated HTML so they execute before React
|
|
29
|
+
* hydration, eliminating the content flash that useEffect-based injection
|
|
30
|
+
* cannot prevent.
|
|
31
|
+
*
|
|
32
|
+
* Usage in app/layout.tsx:
|
|
33
|
+
*
|
|
34
|
+
* import { MVTScripts } from '@splitlabio/nextjs-orchestrator';
|
|
35
|
+
*
|
|
36
|
+
* export default function RootLayout({ children }) {
|
|
37
|
+
* return (
|
|
38
|
+
* <html>
|
|
39
|
+
* <body>
|
|
40
|
+
* <MVTScripts orchestratorKey="YOUR_PROJECT_KEY" />
|
|
41
|
+
* {children}
|
|
42
|
+
* </body>
|
|
43
|
+
* </html>
|
|
44
|
+
* );
|
|
45
|
+
* }
|
|
46
|
+
*/
|
|
47
|
+
export declare const MVTScripts: React.FC<MVTScriptsProps>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const DEFAULT_ENGINE_URL = 'https://cdn.splitlab.io/cdn/engine.js';
|
|
3
|
+
/**
|
|
4
|
+
* Server Component — place at the top of your root layout.tsx <body>.
|
|
5
|
+
*
|
|
6
|
+
* Renders the anti-flicker inline script and the MVT engine <script> tag
|
|
7
|
+
* directly into the server-generated HTML so they execute before React
|
|
8
|
+
* hydration, eliminating the content flash that useEffect-based injection
|
|
9
|
+
* cannot prevent.
|
|
10
|
+
*
|
|
11
|
+
* Usage in app/layout.tsx:
|
|
12
|
+
*
|
|
13
|
+
* import { MVTScripts } from '@splitlabio/nextjs-orchestrator';
|
|
14
|
+
*
|
|
15
|
+
* export default function RootLayout({ children }) {
|
|
16
|
+
* return (
|
|
17
|
+
* <html>
|
|
18
|
+
* <body>
|
|
19
|
+
* <MVTScripts orchestratorKey="YOUR_PROJECT_KEY" />
|
|
20
|
+
* {children}
|
|
21
|
+
* </body>
|
|
22
|
+
* </html>
|
|
23
|
+
* );
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
export const MVTScripts = ({ orchestratorKey, antiFlickerEnabled = true, antiFlickerTimeoutMs = 3000, engineUrl = DEFAULT_ENGINE_URL, }) => {
|
|
27
|
+
// Matches the exact anti-flicker snippet used in the plain HTML pages:
|
|
28
|
+
// hides body{opacity:0}, exposes window.rmfk() for the engine to call,
|
|
29
|
+
// and falls back to auto-removal after the timeout.
|
|
30
|
+
const antiFlickerHtml = `var timeout=${antiFlickerTimeoutMs};!(function(h,i,d,e){var t,n=h.createElement("style");(n.id=e),(n.innerHTML="body{opacity:0}"),h.head.appendChild(n),(t=d),(i.rmfk=function(){var t=h.getElementById(e);t&&t.parentNode.removeChild(t)}),setTimeout(i.rmfk,t)})(document,window,timeout,"abhide");`;
|
|
31
|
+
return (_jsxs(_Fragment, { children: [antiFlickerEnabled && (
|
|
32
|
+
// eslint-disable-next-line react/no-danger
|
|
33
|
+
_jsx("script", { id: "mvt-anti-flicker", dangerouslySetInnerHTML: { __html: antiFlickerHtml } })), _jsx("script", { id: "mvt-engine-script", src: engineUrl, "data-project-key": orchestratorKey, "data-mvt": "engine", "data-mvt-engine": "true", "data-flicker-class": "abtest-hidden", crossOrigin: "anonymous", async: true })] }));
|
|
34
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@splitlabio/nextjs-orchestrator",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Next.js SDK to integrate the Splitlab CDN engine with optional anti-flicker support.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.build.json"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"next": ">=13.0.0",
|
|
25
|
+
"react": ">=18.0.0",
|
|
26
|
+
"react-dom": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/react": "^18.0.0",
|
|
30
|
+
"@types/react-dom": "^18.0.0",
|
|
31
|
+
"typescript": "^5.6.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"nextjs",
|
|
35
|
+
"splitlab",
|
|
36
|
+
"ab-testing",
|
|
37
|
+
"multivariate-testing",
|
|
38
|
+
"cdn-engine"
|
|
39
|
+
],
|
|
40
|
+
"author": "Splitlab",
|
|
41
|
+
"license": "Splitlab.io",
|
|
42
|
+
"packageManager": "yarn@4.12.0"
|
|
43
|
+
}
|