@mvtlab/nextjs-orchestrator 0.1.0
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 +10 -0
- package/README.md +204 -0
- package/dist/MVTOrchestrator.d.ts +29 -0
- package/dist/MVTOrchestrator.js +57 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
MVTLab.io License
|
|
2
|
+
|
|
3
|
+
Copyright (c) MVTLab.io. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is proprietary and confidential to MVTLab.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 MVTLab.io.
|
|
9
|
+
|
|
10
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
## `@mvtlab/nextjs-orchestrator`
|
|
2
|
+
|
|
3
|
+
**Next.js / React SDK** to integrate the MVTLab.io CDN engine with optional anti‑flicker support.
|
|
4
|
+
|
|
5
|
+
It wraps the plain HTML snippet:
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<script>
|
|
9
|
+
var timeout = 3000;
|
|
10
|
+
!(function (h, i, d, e) {
|
|
11
|
+
var t,
|
|
12
|
+
n = h.createElement('style');
|
|
13
|
+
(n.id = e),
|
|
14
|
+
(n.innerHTML = 'body{opacity:0}'),
|
|
15
|
+
h.head.appendChild(n),
|
|
16
|
+
(t = d),
|
|
17
|
+
(i.rmfk = function () {
|
|
18
|
+
var t = h.getElementById(e);
|
|
19
|
+
t && t.parentNode.removeChild(t);
|
|
20
|
+
}),
|
|
21
|
+
setTimeout(i.rmfk, t);
|
|
22
|
+
})(document, window, timeout, 'abhide');
|
|
23
|
+
</script>
|
|
24
|
+
<script
|
|
25
|
+
src="https://staging-svc.mvtlab.io/scripts/engine.js"
|
|
26
|
+
data-project-key="Kjnqjkf277nYGYUSoA0IXQ"
|
|
27
|
+
data-mvt="engine"
|
|
28
|
+
></script>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
into a simple React component:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<MVTOrchestrator orchestratorKey="yourProjectKey" antiFlickerEnabled>
|
|
35
|
+
{children}
|
|
36
|
+
</MVTOrchestrator>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @mvtlab/nextjs-orchestrator
|
|
45
|
+
# or
|
|
46
|
+
yarn add @mvtlab/nextjs-orchestrator
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`MVTOrchestrator` is a **client component**, so it must be used in a file marked with `'use client';`.
|
|
50
|
+
|
|
51
|
+
> **Why client-only?**
|
|
52
|
+
> The orchestrator injects `<script>` and `<style>` tags, accesses `window` and `document`, and defines `window.rmfk`.
|
|
53
|
+
> These operations only exist in the browser and are not allowed in Next.js server components, so the component must run on the client.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### Quick start (Next.js App Router)
|
|
58
|
+
|
|
59
|
+
Recommended usage is to wrap your **entire app at the root** so there is exactly **one project key per website**.
|
|
60
|
+
|
|
61
|
+
Example in `app/layout.tsx`:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
'use client';
|
|
65
|
+
|
|
66
|
+
import React from 'react';
|
|
67
|
+
import { MVTOrchestrator } from '@mvtlab/nextjs-orchestrator';
|
|
68
|
+
|
|
69
|
+
export default function RootLayout({
|
|
70
|
+
children,
|
|
71
|
+
}: {
|
|
72
|
+
children: React.ReactNode;
|
|
73
|
+
}) {
|
|
74
|
+
return (
|
|
75
|
+
<html lang="en">
|
|
76
|
+
<body>
|
|
77
|
+
<MVTOrchestrator
|
|
78
|
+
orchestratorKey="Kjnqjkf277nYGYUSoA0IXQ"
|
|
79
|
+
antiFlickerEnabled={true}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
</MVTOrchestrator>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This:
|
|
90
|
+
|
|
91
|
+
- **Injects the MVTLab.io engine script** into `<head>` once for the whole site with your project key.
|
|
92
|
+
- **Optionally hides the body** until the engine is ready (anti‑flicker).
|
|
93
|
+
In this setup your app has **a single `orchestratorKey` at the root**, shared across all pages.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### Usage in Next.js Pages Router
|
|
98
|
+
|
|
99
|
+
In a classic `pages/_app.tsx`:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// pages/_app.tsx
|
|
103
|
+
'use client';
|
|
104
|
+
|
|
105
|
+
import type { AppProps } from 'next/app';
|
|
106
|
+
import { MVTOrchestrator } from '@mvtlab/nextjs-orchestrator';
|
|
107
|
+
|
|
108
|
+
export default function MyApp({ Component, pageProps }: AppProps) {
|
|
109
|
+
return (
|
|
110
|
+
<MVTOrchestrator
|
|
111
|
+
orchestratorKey="Kjnqjkf277nYGYUSoA0IXQ"
|
|
112
|
+
antiFlickerEnabled={true}
|
|
113
|
+
>
|
|
114
|
+
<Component {...pageProps} />
|
|
115
|
+
</MVTOrchestrator>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
In this setup your app has **a single `orchestratorKey` at the root**, shared across all pages.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Props
|
|
125
|
+
|
|
126
|
+
- **`orchestratorKey`** (`string`, required)
|
|
127
|
+
- MVTLab.io project key.
|
|
128
|
+
- Passed to the script as `data-project-key="<orchestratorKey>"`.
|
|
129
|
+
|
|
130
|
+
- **`antiFlickerEnabled`** (`boolean`, default `true`)
|
|
131
|
+
- When `true`, a temporary `<style id="abhide">body{opacity:0}</style>` is injected into `<head>`.
|
|
132
|
+
- This mimics the original anti‑flicker snippet and prevents users from briefly seeing the “un‑optimized” variant.
|
|
133
|
+
|
|
134
|
+
- **`antiFlickerTimeoutMs`** (`number`, default `3000`)
|
|
135
|
+
- Timeout (in ms) after which the anti‑flicker style is removed automatically.
|
|
136
|
+
- Safety net in case the engine never calls `window.rmfk()`.
|
|
137
|
+
|
|
138
|
+
- **`engineUrl`** (`string`, default `https://staging-svc.mvtlab.io/scripts/engine.js`)
|
|
139
|
+
- Script URL used to load the MVTLab.io engine.
|
|
140
|
+
- You can override this if you have a different environment (e.g. production).
|
|
141
|
+
|
|
142
|
+
- **`children`** (`ReactNode`, required)
|
|
143
|
+
- The part of your app that should be orchestrated by the engine.
|
|
144
|
+
- Usually your entire app tree.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### How anti‑flicker works
|
|
149
|
+
|
|
150
|
+
When `antiFlickerEnabled` is `true`:
|
|
151
|
+
|
|
152
|
+
- A `<style>` tag with `id="abhide"` and `body{opacity:0}` is injected into `<head>`.
|
|
153
|
+
- `window.rmfk` is defined as a function that removes that style element.
|
|
154
|
+
- A timeout (`antiFlickerTimeoutMs`, default 3000ms) calls `window.rmfk()` as a fallback.
|
|
155
|
+
|
|
156
|
+
This is a **direct React translation** of the plain HTML snippet you would otherwise manually embed.
|
|
157
|
+
|
|
158
|
+
If you do **not** want anti‑flicker:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<MVTOrchestrator orchestratorKey="yourProjectKey" antiFlickerEnabled={false}>
|
|
162
|
+
{children}
|
|
163
|
+
</MVTOrchestrator>
|
|
164
|
+
``>
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### How script loading works
|
|
169
|
+
|
|
170
|
+
On the client, the component:
|
|
171
|
+
|
|
172
|
+
1. Checks for an existing `<script>` with `id="mvt-engine-script"`.
|
|
173
|
+
2. If not present, it creates a new `<script>` element with:
|
|
174
|
+
- `src` = `engineUrl` (default staging URL),
|
|
175
|
+
- `data-project-key` = `orchestratorKey`,
|
|
176
|
+
- `data-mvt` = `"engine"`,
|
|
177
|
+
- `async = true`.
|
|
178
|
+
3. Appends the script to `<head>`.
|
|
179
|
+
|
|
180
|
+
This ensures **only one script is injected per page**, even if `MVTOrchestrator` is rendered multiple times.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### FAQ
|
|
185
|
+
|
|
186
|
+
- **Is this safe with SSR?**
|
|
187
|
+
Yes. All DOM access runs inside `useEffect`, so it only executes in the browser, not during server‑side rendering.
|
|
188
|
+
|
|
189
|
+
- **Can I use multiple project keys on the same page or site?**
|
|
190
|
+
**No.** The orchestrator is designed to be used **once at the root of your app** with a single `orchestratorKey` for the whole website.
|
|
191
|
+
Rendering with different keys on the same page is not supported and will log an error in the console as a safety check.
|
|
192
|
+
|
|
193
|
+
- **Can I disable anti‑flicker in development?**
|
|
194
|
+
Yes, just pass `antiFlickerEnabled={false}` when you don’t need it.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### License
|
|
199
|
+
|
|
200
|
+
This package is **licensed under MVTLab.io**.
|
|
201
|
+
|
|
202
|
+
Copyright © MVTLab.io. All rights reserved.
|
|
203
|
+
|
|
204
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
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, a temporary style is applied to hide the body until
|
|
11
|
+
* the engine (or timeout) removes it, similar to the plain HTML snippet.
|
|
12
|
+
*/
|
|
13
|
+
antiFlickerEnabled?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Timeout in milliseconds to automatically remove the anti-flicker style.
|
|
16
|
+
* Defaults to 3000ms.
|
|
17
|
+
*/
|
|
18
|
+
antiFlickerTimeoutMs?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Custom engine script URL.
|
|
21
|
+
* Defaults to the staging endpoint used in the original snippet.
|
|
22
|
+
*/
|
|
23
|
+
engineUrl?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Children that will be rendered inside the orchestrator.
|
|
26
|
+
*/
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
};
|
|
29
|
+
export declare const MVTOrchestrator: React.FC<MVTOrchestratorProps>;
|
|
@@ -0,0 +1,57 @@
|
|
|
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://staging-svc.mvtlab.io/scripts/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
|
+
// Anti-flicker: inject style to hide body and set up removal function
|
|
12
|
+
if (antiFlickerEnabled) {
|
|
13
|
+
const existing = document.getElementById(ANTI_FLICKER_STYLE_ID);
|
|
14
|
+
if (!existing) {
|
|
15
|
+
const style = document.createElement('style');
|
|
16
|
+
style.id = ANTI_FLICKER_STYLE_ID;
|
|
17
|
+
style.innerHTML = 'body{opacity:0}';
|
|
18
|
+
document.head.appendChild(style);
|
|
19
|
+
}
|
|
20
|
+
// Expose rmfk on window to allow the engine to remove the flicker style
|
|
21
|
+
window.rmfk = function rmfk() {
|
|
22
|
+
const el = document.getElementById(ANTI_FLICKER_STYLE_ID);
|
|
23
|
+
if (el && el.parentNode) {
|
|
24
|
+
el.parentNode.removeChild(el);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
// Safety timeout to remove if engine never calls rmfk
|
|
28
|
+
window.setTimeout(() => {
|
|
29
|
+
if (typeof window.rmfk === 'function') {
|
|
30
|
+
window.rmfk();
|
|
31
|
+
}
|
|
32
|
+
}, antiFlickerTimeoutMs);
|
|
33
|
+
}
|
|
34
|
+
// Enforce a single project key per page
|
|
35
|
+
const globalKeyProp = '__mvtOrchestratorKey';
|
|
36
|
+
const w = window;
|
|
37
|
+
if (w[globalKeyProp] && w[globalKeyProp] !== orchestratorKey) {
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
console.error('[MVTOrchestrator] Multiple different project keys on the same page are not supported. ' +
|
|
40
|
+
`Existing key: "${w[globalKeyProp]}", new key: "${orchestratorKey}".`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
w[globalKeyProp] = orchestratorKey;
|
|
44
|
+
// Inject the engine script only once per page load / key
|
|
45
|
+
const scriptId = 'mvt-engine-script';
|
|
46
|
+
if (!document.getElementById(scriptId)) {
|
|
47
|
+
const script = document.createElement('script');
|
|
48
|
+
script.id = scriptId;
|
|
49
|
+
script.src = engineUrl;
|
|
50
|
+
script.async = true;
|
|
51
|
+
script.setAttribute('data-project-key', orchestratorKey);
|
|
52
|
+
script.setAttribute('data-mvt', 'engine');
|
|
53
|
+
document.head.appendChild(script);
|
|
54
|
+
}
|
|
55
|
+
}, [orchestratorKey, antiFlickerEnabled, antiFlickerTimeoutMs, engineUrl]);
|
|
56
|
+
return _jsx(_Fragment, { children: children });
|
|
57
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MVTOrchestrator';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MVTOrchestrator';
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mvtlab/nextjs-orchestrator",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Next.js SDK to integrate the MVT Lab CDN engine with optional anti-flicker support.",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.build.json"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"next": ">=13.0.0",
|
|
14
|
+
"react": ">=18.0.0",
|
|
15
|
+
"react-dom": ">=18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/react": "^18.0.0",
|
|
19
|
+
"@types/react-dom": "^18.0.0",
|
|
20
|
+
"typescript": "^5.6.0"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"nextjs",
|
|
24
|
+
"mvt",
|
|
25
|
+
"ab-testing",
|
|
26
|
+
"cdn-engine"
|
|
27
|
+
],
|
|
28
|
+
"author": "MVT Lab",
|
|
29
|
+
"license": "MVTLab.io"
|
|
30
|
+
}
|