@preprio/prepr-nextjs 2.0.0-alpha.8 → 2.0.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/README.md +731 -106
- package/dist/chunk-QZTUT4DH.js +2 -0
- package/dist/chunk-QZTUT4DH.js.map +1 -0
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/middleware/index.cjs +1 -1
- package/dist/middleware/index.cjs.map +1 -1
- package/dist/middleware/index.d.cts +20 -4
- package/dist/middleware/index.d.ts +20 -4
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/index.js.map +1 -1
- package/dist/react/index.cjs +6 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +57 -19
- package/dist/react/index.d.ts +57 -19
- package/dist/react/index.js +6 -1
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +2 -2
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +50 -11
- package/dist/server/index.d.ts +50 -11
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/types/index.d.cts +33 -13
- package/dist/types/index.d.ts +33 -13
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +39 -9
- package/dist/utils/index.d.ts +39 -9
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +13 -24
package/README.md
CHANGED
|
@@ -1,170 +1,795 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Prepr Next.js Package
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<hr>
|
|
5
|
-
The Prepr Next.js package offers some helper functions and a preview toolbar component for an
|
|
6
|
-
easier personalization & A/B testing implementation in your Next.js project.
|
|
3
|
+
A powerful TypeScript library that provides preview functionality, visual editing capabilities, and A/B testing for [Prepr CMS](https://prepr.io) integrated with Next.js applications.
|
|
7
4
|
|
|
8
|
-
##
|
|
9
|
-
<hr>
|
|
10
|
-
To install the Prepr Next.js package, run the following command:
|
|
5
|
+
## ⚡ Quick Start
|
|
11
6
|
|
|
12
7
|
```bash
|
|
8
|
+
# Install the package
|
|
13
9
|
npm install @preprio/prepr-nextjs
|
|
10
|
+
# or
|
|
11
|
+
pnpm add @preprio/prepr-nextjs
|
|
14
12
|
```
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
Add environment variables to your `.env`:
|
|
17
15
|
|
|
18
16
|
```bash
|
|
17
|
+
PREPR_GRAPHQL_URL=https://graphql.prepr.io/{YOUR_ACCESS_TOKEN}
|
|
19
18
|
PREPR_ENV=preview
|
|
20
19
|
```
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Set up middleware in `middleware.ts`:
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
```typescript
|
|
24
|
+
import type { NextRequest } from 'next/server'
|
|
25
|
+
import createPreprMiddleware from '@preprio/prepr-nextjs/middleware'
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
export function middleware(request: NextRequest) {
|
|
28
|
+
return createPreprMiddleware(request, { preview: true })
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Add toolbar and tracking to your layout:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { getToolbarProps, extractAccessToken } from '@preprio/prepr-nextjs/server'
|
|
36
|
+
import { PreprToolbar, PreprToolbarProvider, PreprTrackingPixel } from '@preprio/prepr-nextjs/react'
|
|
37
|
+
import '@preprio/prepr-nextjs/index.css'
|
|
38
|
+
|
|
39
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
40
|
+
const toolbarProps = await getToolbarProps(process.env.PREPR_GRAPHQL_URL!)
|
|
41
|
+
const accessToken = extractAccessToken(process.env.PREPR_GRAPHQL_URL!)
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<html>
|
|
45
|
+
<head>
|
|
46
|
+
{accessToken && <PreprTrackingPixel accessToken={accessToken!} />}
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
<PreprToolbarProvider props={toolbarProps}>
|
|
50
|
+
<PreprToolbar />
|
|
51
|
+
{children}
|
|
52
|
+
</PreprToolbarProvider>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 📋 Prerequisites
|
|
60
|
+
|
|
61
|
+
Before installing, ensure you have:
|
|
62
|
+
|
|
63
|
+
- **Next.js 13.0.0 or later** (supports App Router)
|
|
64
|
+
- **React 17.0.0 or later** (React 18+ recommended)
|
|
65
|
+
- **Node.js 16.0.0 or later**
|
|
66
|
+
- **A Prepr account**
|
|
67
|
+
- **Prepr GraphQL URL** (found in Settings → Access tokens)
|
|
68
|
+
|
|
69
|
+
### Prepr Account Setup
|
|
70
|
+
|
|
71
|
+
1. **Create a Prepr account** at [prepr.io](https://prepr.io)
|
|
72
|
+
2. **Get your GraphQL URL**:
|
|
73
|
+
- Go to Settings → Access tokens
|
|
74
|
+
- Find your GraphQL Preview access token
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
- Copy the full GraphQL URL (e.g., `https://graphql.prepr.io/e6f7a0521f11e5149ce65b0e9f372ced2dfc923490890e7f225da1db84cxxxxx`)
|
|
79
|
+
- The URL format is always `https://graphql.prepr.io/{YOUR_ACCESS_TOKEN}`
|
|
80
|
+
3. **Enable edit mode** (for toolbar):
|
|
81
|
+
- Open your GraphQL Preview access token
|
|
82
|
+
- Check "Enable edit mode"
|
|
83
|
+
- Save the token
|
|
84
|
+
|
|
85
|
+

|
|
86
|
+
|
|
87
|
+
## 🔧 Installation & Setup
|
|
88
|
+
|
|
89
|
+
### 1. Install the Package
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install @preprio/prepr-nextjs
|
|
93
|
+
# or
|
|
94
|
+
pnpm add @preprio/prepr-nextjs
|
|
95
|
+
# or
|
|
96
|
+
yarn add @preprio/prepr-nextjs
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Environment Configuration
|
|
100
|
+
|
|
101
|
+
Create or update your `.env` file:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Required: Your Prepr GraphQL endpoint
|
|
105
|
+
PREPR_GRAPHQL_URL=https://graphql.prepr.io/{YOUR_ACCESS_TOKEN}
|
|
106
|
+
|
|
107
|
+
# Required: Environment mode
|
|
108
|
+
PREPR_ENV=preview # Use 'preview' for staging/development
|
|
109
|
+
# PREPR_ENV=production # Use 'production' for live sites
|
|
110
|
+
|
|
111
|
+
# Optional: Enable debug logging (development only)
|
|
112
|
+
# PREPR_DEBUG=true
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> **Important**: Replace `{YOUR_ACCESS_TOKEN}` with your actual Prepr access token from Settings → Access tokens.
|
|
116
|
+
|
|
117
|
+
### 3. Middleware Setup
|
|
118
|
+
|
|
119
|
+
The middleware handles personalization headers, customer ID tracking, and preview mode functionality.
|
|
120
|
+
|
|
121
|
+
#### TypeScript Setup
|
|
122
|
+
|
|
123
|
+
Create or update `middleware.ts` in your project root:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
29
126
|
import type { NextRequest } from 'next/server'
|
|
30
127
|
import createPreprMiddleware from '@preprio/prepr-nextjs/middleware'
|
|
31
128
|
|
|
32
129
|
export function middleware(request: NextRequest) {
|
|
33
|
-
|
|
130
|
+
return createPreprMiddleware(request, {
|
|
131
|
+
preview: process.env.PREPR_ENV === 'preview'
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const config = {
|
|
136
|
+
matcher: [
|
|
137
|
+
/*
|
|
138
|
+
* Match all request paths except for the ones starting with:
|
|
139
|
+
* - api (API routes)
|
|
140
|
+
* - _next/static (static files)
|
|
141
|
+
* - _next/image (image optimization files)
|
|
142
|
+
* - favicon.ico (favicon file)
|
|
143
|
+
*/
|
|
144
|
+
'/((?!api|_next/static|_next/image|favicon.ico).*)',
|
|
145
|
+
],
|
|
34
146
|
}
|
|
35
147
|
```
|
|
36
148
|
|
|
37
|
-
|
|
149
|
+
#### JavaScript Setup
|
|
150
|
+
|
|
151
|
+
Create or update `middleware.js` in your project root:
|
|
152
|
+
|
|
38
153
|
```javascript
|
|
39
154
|
import createPreprMiddleware from '@preprio/prepr-nextjs/middleware'
|
|
40
155
|
|
|
41
|
-
export async function middleware(request
|
|
42
|
-
|
|
156
|
+
export async function middleware(request) {
|
|
157
|
+
return createPreprMiddleware(request, {
|
|
158
|
+
preview: process.env.PREPR_ENV === 'preview'
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const config = {
|
|
163
|
+
matcher: [
|
|
164
|
+
'/((?!api|_next/static|_next/image|favicon.ico).*)',
|
|
165
|
+
],
|
|
43
166
|
}
|
|
44
167
|
```
|
|
45
168
|
|
|
46
|
-
|
|
47
|
-
The `PreprMiddleware` accepts a request and optional response property and returns a `NextRequest` object.
|
|
48
|
-
This is done so you are able to chain your own middleware to it.
|
|
169
|
+
#### Chaining with Existing Middleware
|
|
49
170
|
|
|
50
|
-
|
|
171
|
+
##### Basic Chaining Pattern
|
|
51
172
|
|
|
52
|
-
|
|
53
|
-
|
|
173
|
+
```typescript
|
|
174
|
+
import type { NextRequest, NextResponse } from 'next/server'
|
|
175
|
+
import createPreprMiddleware from '@preprio/prepr-nextjs/middleware'
|
|
176
|
+
|
|
177
|
+
export function middleware(request: NextRequest) {
|
|
178
|
+
// Start with your existing middleware
|
|
179
|
+
let response = NextResponse.next()
|
|
180
|
+
|
|
181
|
+
// Add your custom logic
|
|
182
|
+
if (request.nextUrl.pathname.startsWith('/admin')) {
|
|
183
|
+
response.headers.set('x-admin-route', 'true')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Chain with Prepr middleware
|
|
187
|
+
return createPreprMiddleware(request, response, {
|
|
188
|
+
preview: process.env.PREPR_ENV === 'preview'
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
```
|
|
54
192
|
|
|
55
|
-
|
|
56
|
-
To set your API request headers to query adaptive content or A/B testing content, you can call the `getPreprHeaders()` helper function. It returns an array of headers that you can spread in your fetch call.
|
|
57
|
-
See the example code below in the `page.tsx` file.
|
|
193
|
+
##### With next-intl Integration
|
|
58
194
|
|
|
59
|
-
```
|
|
195
|
+
```typescript
|
|
196
|
+
import type { NextRequest } from 'next/server'
|
|
197
|
+
import createIntlMiddleware from 'next-intl/middleware'
|
|
198
|
+
import createPreprMiddleware from '@preprio/prepr-nextjs/middleware'
|
|
199
|
+
|
|
200
|
+
const intlMiddleware = createIntlMiddleware({
|
|
201
|
+
locales: ['en', 'de', 'fr'],
|
|
202
|
+
defaultLocale: 'en'
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
export function middleware(request: NextRequest) {
|
|
206
|
+
// First run internationalization middleware
|
|
207
|
+
const intlResponse = intlMiddleware(request)
|
|
208
|
+
|
|
209
|
+
// If next-intl returns a redirect, return it immediately
|
|
210
|
+
if (intlResponse.status >= 300 && intlResponse.status < 400) {
|
|
211
|
+
return intlResponse
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Otherwise, chain with Prepr middleware
|
|
215
|
+
return createPreprMiddleware(request, intlResponse, {
|
|
216
|
+
preview: process.env.PREPR_ENV === 'preview'
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const config = {
|
|
221
|
+
matcher: [
|
|
222
|
+
'/((?!api|_next/static|_next/image|favicon.ico).*)',
|
|
223
|
+
],
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
##### Advanced Chaining with Multiple Middlewares
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import type { NextRequest, NextResponse } from 'next/server'
|
|
231
|
+
import createPreprMiddleware from '@preprio/prepr-nextjs/middleware'
|
|
232
|
+
import { authMiddleware } from '@/lib/auth'
|
|
233
|
+
import { rateLimitMiddleware } from '@/lib/rate-limit'
|
|
234
|
+
|
|
235
|
+
export function middleware(request: NextRequest) {
|
|
236
|
+
let response = NextResponse.next()
|
|
237
|
+
|
|
238
|
+
// 1. Rate limiting
|
|
239
|
+
response = rateLimitMiddleware(request, response)
|
|
240
|
+
|
|
241
|
+
// 2. Authentication (if needed)
|
|
242
|
+
if (request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
243
|
+
response = authMiddleware(request, response)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 3. Custom headers
|
|
247
|
+
response.headers.set('x-custom-header', 'my-value')
|
|
248
|
+
|
|
249
|
+
// 4. Finally, Prepr middleware
|
|
250
|
+
return createPreprMiddleware(request, response, {
|
|
251
|
+
preview: process.env.PREPR_ENV === 'preview'
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
##### Conditional Chaining
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import type { NextRequest, NextResponse } from 'next/server'
|
|
260
|
+
import createPreprMiddleware from '@preprio/prepr-nextjs/middleware'
|
|
261
|
+
|
|
262
|
+
export function middleware(request: NextRequest) {
|
|
263
|
+
const { pathname } = request.nextUrl
|
|
264
|
+
|
|
265
|
+
// Skip Prepr middleware for API routes
|
|
266
|
+
if (pathname.startsWith('/api/')) {
|
|
267
|
+
return NextResponse.next()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Apply different logic based on route
|
|
271
|
+
let response = NextResponse.next()
|
|
272
|
+
|
|
273
|
+
if (pathname.startsWith('/blog')) {
|
|
274
|
+
response.headers.set('x-content-type', 'blog')
|
|
275
|
+
} else if (pathname.startsWith('/product')) {
|
|
276
|
+
response.headers.set('x-content-type', 'product')
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Always apply Prepr middleware for content routes
|
|
280
|
+
return createPreprMiddleware(request, response, {
|
|
281
|
+
preview: process.env.PREPR_ENV === 'preview'
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 4. Layout Integration
|
|
287
|
+
|
|
288
|
+
#### App Router (Next.js 13+)
|
|
289
|
+
|
|
290
|
+
The toolbar should only be rendered in preview environments to avoid showing development tools in production. Here are several approaches for conditional rendering:
|
|
291
|
+
|
|
292
|
+
##### Basic Conditional Rendering
|
|
293
|
+
|
|
294
|
+
Update your `app/layout.tsx`:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { getToolbarProps } from '@preprio/prepr-nextjs/server'
|
|
298
|
+
import {
|
|
299
|
+
PreprToolbar,
|
|
300
|
+
PreprToolbarProvider
|
|
301
|
+
} from '@preprio/prepr-nextjs/react'
|
|
302
|
+
import '@preprio/prepr-nextjs/index.css'
|
|
303
|
+
|
|
304
|
+
export default async function RootLayout({
|
|
305
|
+
children,
|
|
306
|
+
}: {
|
|
307
|
+
children: React.ReactNode
|
|
308
|
+
}) {
|
|
309
|
+
const isPreview = process.env.PREPR_ENV === 'preview'
|
|
310
|
+
const toolbarProps = isPreview ? await getToolbarProps(process.env.PREPR_GRAPHQL_URL!) : null
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<html lang="en">
|
|
314
|
+
<body>
|
|
315
|
+
{isPreview && toolbarProps ? (
|
|
316
|
+
<PreprToolbarProvider props={toolbarProps}>
|
|
317
|
+
<PreprToolbar />
|
|
318
|
+
{children}
|
|
319
|
+
</PreprToolbarProvider>
|
|
320
|
+
) : (
|
|
321
|
+
children
|
|
322
|
+
)}
|
|
323
|
+
</body>
|
|
324
|
+
</html>
|
|
325
|
+
)
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
##### Advanced Conditional Rendering with Error Handling
|
|
330
|
+
|
|
331
|
+
For production applications, you may want more robust error handling:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { getToolbarProps } from '@preprio/prepr-nextjs/server'
|
|
335
|
+
import {
|
|
336
|
+
PreprToolbar,
|
|
337
|
+
PreprToolbarProvider
|
|
338
|
+
} from '@preprio/prepr-nextjs/react'
|
|
339
|
+
import '@preprio/prepr-nextjs/index.css'
|
|
340
|
+
|
|
341
|
+
export default async function RootLayout({
|
|
342
|
+
children,
|
|
343
|
+
}: {
|
|
344
|
+
children: React.ReactNode
|
|
345
|
+
}) {
|
|
346
|
+
const isPreview = process.env.PREPR_ENV === 'preview'
|
|
347
|
+
const graphqlUrl = process.env.PREPR_GRAPHQL_URL
|
|
348
|
+
|
|
349
|
+
// Only fetch toolbar props in preview mode with valid URL
|
|
350
|
+
let toolbarProps = null
|
|
351
|
+
if (isPreview && graphqlUrl) {
|
|
352
|
+
try {
|
|
353
|
+
toolbarProps = await getToolbarProps(graphqlUrl)
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error('Failed to load toolbar:', error)
|
|
356
|
+
// Continue without toolbar instead of breaking the app
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<html lang="en">
|
|
362
|
+
<body>
|
|
363
|
+
{isPreview && toolbarProps ? (
|
|
364
|
+
<PreprToolbarProvider props={toolbarProps}>
|
|
365
|
+
<PreprToolbar />
|
|
366
|
+
{children}
|
|
367
|
+
</PreprToolbarProvider>
|
|
368
|
+
) : (
|
|
369
|
+
children
|
|
370
|
+
)}
|
|
371
|
+
</body>
|
|
372
|
+
</html>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
##### Component-Level Conditional Rendering
|
|
378
|
+
|
|
379
|
+
For better separation of concerns, create a dedicated component:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
// components/PreviewWrapper.tsx
|
|
383
|
+
import { getToolbarProps } from '@preprio/prepr-nextjs/server'
|
|
384
|
+
import {
|
|
385
|
+
PreprToolbar,
|
|
386
|
+
PreprToolbarProvider
|
|
387
|
+
} from '@preprio/prepr-nextjs/react'
|
|
388
|
+
|
|
389
|
+
interface PreviewWrapperProps {
|
|
390
|
+
children: React.ReactNode
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export default async function PreviewWrapper({ children }: PreviewWrapperProps) {
|
|
394
|
+
const isPreview = process.env.PREPR_ENV === 'preview'
|
|
395
|
+
|
|
396
|
+
if (!isPreview) {
|
|
397
|
+
return <>{children}</>
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const toolbarProps = await getToolbarProps(process.env.PREPR_GRAPHQL_URL!)
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<PreprToolbarProvider props={toolbarProps}>
|
|
404
|
+
<PreprToolbar />
|
|
405
|
+
{children}
|
|
406
|
+
</PreprToolbarProvider>
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// app/layout.tsx
|
|
411
|
+
import PreviewWrapper from '@/components/PreviewWrapper'
|
|
412
|
+
import '@preprio/prepr-nextjs/index.css'
|
|
413
|
+
|
|
414
|
+
export default function RootLayout({
|
|
415
|
+
children,
|
|
416
|
+
}: {
|
|
417
|
+
children: React.ReactNode
|
|
418
|
+
}) {
|
|
419
|
+
return (
|
|
420
|
+
<html lang="en">
|
|
421
|
+
<body>
|
|
422
|
+
<PreviewWrapper>
|
|
423
|
+
{children}
|
|
424
|
+
</PreviewWrapper>
|
|
425
|
+
</body>
|
|
426
|
+
</html>
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### Why Conditional Rendering Matters
|
|
432
|
+
|
|
433
|
+
1. **Performance**: Prevents unnecessary API calls in production
|
|
434
|
+
2. **Security**: Avoids exposing preview functionality to end users
|
|
435
|
+
3. **Bundle Size**: Excludes toolbar code from production builds
|
|
436
|
+
4. **User Experience**: Ensures clean production UI without development tools
|
|
437
|
+
|
|
438
|
+
#### Best Practices for Conditional Rendering
|
|
439
|
+
|
|
440
|
+
- **Environment Variables**: Always use environment variables to control preview mode
|
|
441
|
+
- **Error Boundaries**: Wrap preview components in error boundaries to prevent crashes
|
|
442
|
+
- **Fallback UI**: Always provide a fallback when toolbar fails to load
|
|
443
|
+
- **TypeScript Safety**: Use proper type guards when checking conditions
|
|
444
|
+
- **Bundle Optimization**: Consider dynamic imports for preview-only code
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
// Dynamic import example for advanced optimization
|
|
448
|
+
const ToolbarDynamic = dynamic(
|
|
449
|
+
() => import('@preprio/prepr-nextjs/react').then(mod => mod.PreprToolbar),
|
|
450
|
+
{
|
|
451
|
+
ssr: false,
|
|
452
|
+
loading: () => <div>Loading preview tools...</div>
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### 5. User Tracking Setup
|
|
458
|
+
|
|
459
|
+
The tracking pixel is essential for collecting user interaction data and enabling personalization features. It should be included in all environments (both preview and production).
|
|
460
|
+
|
|
461
|
+
#### Basic Setup
|
|
462
|
+
|
|
463
|
+
Add the tracking pixel to your layout's `<head>` section:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
import { extractAccessToken } from '@preprio/prepr-nextjs/server'
|
|
467
|
+
import { PreprTrackingPixel } from '@preprio/prepr-nextjs/react'
|
|
468
|
+
|
|
469
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
470
|
+
const accessToken = extractAccessToken(process.env.PREPR_GRAPHQL_URL!)
|
|
471
|
+
|
|
472
|
+
return (
|
|
473
|
+
<html>
|
|
474
|
+
<head>
|
|
475
|
+
{accessToken && <PreprTrackingPixel accessToken={accessToken!} />}
|
|
476
|
+
</head>
|
|
477
|
+
<body>{children}</body>
|
|
478
|
+
</html>
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### Alternative: Body Placement
|
|
484
|
+
|
|
485
|
+
You can also place the tracking pixel in the body if needed:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
489
|
+
const accessToken = extractAccessToken(process.env.PREPR_GRAPHQL_URL!)
|
|
490
|
+
|
|
491
|
+
return (
|
|
492
|
+
<html>
|
|
493
|
+
<body>
|
|
494
|
+
{accessToken && <PreprTrackingPixel accessToken={accessToken!} />}
|
|
495
|
+
{children}
|
|
496
|
+
</body>
|
|
497
|
+
</html>
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 6. API Integration
|
|
503
|
+
|
|
504
|
+
Use the `getPreprHeaders()` helper function in your data fetching to enable personalization and A/B testing:
|
|
505
|
+
|
|
506
|
+
#### With Apollo Client
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
60
509
|
import { getClient } from '@/lib/client'
|
|
61
|
-
import { GetPageBySlugDocument
|
|
510
|
+
import { GetPageBySlugDocument } from '@/gql/graphql'
|
|
62
511
|
import { getPreprHeaders } from '@preprio/prepr-nextjs/server'
|
|
63
512
|
|
|
64
|
-
const getData = async () => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
fetchPolicy: 'no-cache',
|
|
76
|
-
})
|
|
513
|
+
const getData = async (slug: string) => {
|
|
514
|
+
const { data } = await getClient().query({
|
|
515
|
+
query: GetPageBySlugDocument,
|
|
516
|
+
variables: { slug },
|
|
517
|
+
context: {
|
|
518
|
+
headers: await getPreprHeaders(),
|
|
519
|
+
},
|
|
520
|
+
fetchPolicy: 'no-cache',
|
|
521
|
+
})
|
|
522
|
+
return data
|
|
77
523
|
}
|
|
78
524
|
```
|
|
79
|
-
See the JavaScript example code below in the `page.js`file.
|
|
80
525
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
526
|
+
#### With Fetch API
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
84
529
|
import { getPreprHeaders } from '@preprio/prepr-nextjs/server'
|
|
85
530
|
|
|
86
|
-
const getData = async () => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
531
|
+
const getData = async (slug: string) => {
|
|
532
|
+
const headers = await getPreprHeaders()
|
|
533
|
+
|
|
534
|
+
const response = await fetch(process.env.PREPR_GRAPHQL_URL!, {
|
|
535
|
+
method: 'POST',
|
|
536
|
+
headers: {
|
|
537
|
+
'Content-Type': 'application/json',
|
|
538
|
+
...headers,
|
|
92
539
|
},
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
540
|
+
body: JSON.stringify({
|
|
541
|
+
query: `
|
|
542
|
+
query GetPageBySlug($slug: String!) {
|
|
543
|
+
Page(slug: $slug) {
|
|
544
|
+
title
|
|
545
|
+
content
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
`,
|
|
549
|
+
variables: { slug },
|
|
550
|
+
}),
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
return response.json()
|
|
100
554
|
}
|
|
101
555
|
```
|
|
102
556
|
|
|
103
|
-
|
|
557
|
+
## 🎛️ API Reference
|
|
558
|
+
|
|
559
|
+
### Server Functions
|
|
560
|
+
|
|
561
|
+
#### `getPreprHeaders()`
|
|
562
|
+
Returns all Prepr headers for API requests.
|
|
104
563
|
|
|
105
|
-
|
|
564
|
+
```typescript
|
|
565
|
+
import { getPreprHeaders } from '@preprio/prepr-nextjs/server'
|
|
106
566
|
|
|
107
|
-
|
|
108
|
-
|
|
567
|
+
const headers = await getPreprHeaders()
|
|
568
|
+
// Returns: { 'prepr-customer-id': 'uuid', 'Prepr-Segments': 'segment-id', ... }
|
|
569
|
+
```
|
|
109
570
|
|
|
110
|
-
|
|
571
|
+
#### `getPreprUUID()`
|
|
572
|
+
Returns the current customer ID from headers.
|
|
111
573
|
|
|
112
|
-
|
|
574
|
+
```typescript
|
|
575
|
+
import { getPreprUUID } from '@preprio/prepr-nextjs/server'
|
|
113
576
|
|
|
114
|
-
|
|
577
|
+
const customerId = await getPreprUUID()
|
|
578
|
+
// Returns: 'uuid-string' or null
|
|
579
|
+
```
|
|
115
580
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
import { getPreviewBarProps } from '@preprio/prepr-nextjs/server'
|
|
581
|
+
#### `getActiveSegment()`
|
|
582
|
+
Returns the currently active segment.
|
|
119
583
|
|
|
120
|
-
|
|
121
|
-
import {
|
|
122
|
-
PreprPreviewBar,
|
|
123
|
-
PreprPreviewBarProvider,
|
|
124
|
-
} from '@preprio/prepr-nextjs/react'
|
|
584
|
+
```typescript
|
|
585
|
+
import { getActiveSegment } from '@preprio/prepr-nextjs/server'
|
|
125
586
|
|
|
126
|
-
|
|
127
|
-
|
|
587
|
+
const segment = await getActiveSegment()
|
|
588
|
+
// Returns: 'segment-id' or null
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
#### `getActiveVariant()`
|
|
592
|
+
Returns the currently active A/B testing variant.
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
import { getActiveVariant } from '@preprio/prepr-nextjs/server'
|
|
596
|
+
|
|
597
|
+
const variant = await getActiveVariant()
|
|
598
|
+
// Returns: 'A' | 'B' | null
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
#### `getToolbarProps()`
|
|
602
|
+
Fetches all necessary props for the toolbar component.
|
|
128
603
|
|
|
604
|
+
```typescript
|
|
605
|
+
import { getToolbarProps } from '@preprio/prepr-nextjs/server'
|
|
606
|
+
|
|
607
|
+
const props = await getToolbarProps(process.env.PREPR_GRAPHQL_URL!)
|
|
608
|
+
// Returns: { activeSegment, activeVariant, data }
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
#### `validatePreprToken()`
|
|
612
|
+
Validates a Prepr GraphQL URL format.
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
import { validatePreprToken } from '@preprio/prepr-nextjs/server'
|
|
616
|
+
|
|
617
|
+
const result = validatePreprToken('https://graphql.prepr.io/YOUR_ACCESS_TOKEN')
|
|
618
|
+
// Returns: { valid: boolean, error?: string }
|
|
619
|
+
```
|
|
129
620
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const previewBarProps = await getPreviewBarProps(process.env.PREPR_GRAPHQL_URL!)
|
|
621
|
+
#### `isPreviewMode()`
|
|
622
|
+
Checks if the current environment is in preview mode.
|
|
133
623
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
624
|
+
```typescript
|
|
625
|
+
import { isPreviewMode } from '@preprio/prepr-nextjs/server'
|
|
626
|
+
|
|
627
|
+
const isPreview = isPreviewMode()
|
|
628
|
+
// Returns: boolean
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
#### `extractAccessToken()`
|
|
632
|
+
Extracts the access token from a Prepr GraphQL URL.
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
import { extractAccessToken } from '@preprio/prepr-nextjs/server'
|
|
636
|
+
|
|
637
|
+
const token = extractAccessToken('https://graphql.prepr.io/abc123')
|
|
638
|
+
// Returns: 'abc123' or null
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### React Components
|
|
642
|
+
|
|
643
|
+
#### `PreprToolbarProvider`
|
|
644
|
+
Context provider that wraps your app with toolbar functionality.
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
import { PreprToolbarProvider } from '@preprio/prepr-nextjs/react'
|
|
648
|
+
|
|
649
|
+
<PreprToolbarProvider props={toolbarProps}>
|
|
650
|
+
{children}
|
|
651
|
+
</PreprToolbarProvider>
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
#### `PreprToolbar`
|
|
655
|
+
The main toolbar component.
|
|
656
|
+
|
|
657
|
+
```typescript
|
|
658
|
+
import { PreprToolbar } from '@preprio/prepr-nextjs/react'
|
|
659
|
+
|
|
660
|
+
<PreprToolbar />
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
#### `PreprTrackingPixel`
|
|
664
|
+
User tracking component that loads the Prepr tracking script.
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
import { PreprTrackingPixel } from '@preprio/prepr-nextjs/react'
|
|
668
|
+
|
|
669
|
+
<PreprTrackingPixel accessToken="your-access-token" />
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
## 🔧 Configuration Options
|
|
673
|
+
|
|
674
|
+
### Environment Variables
|
|
675
|
+
|
|
676
|
+
| Variable | Required | Default | Description |
|
|
677
|
+
|----------|----------|---------|-------------|
|
|
678
|
+
| `PREPR_GRAPHQL_URL` | Yes | - | Your Prepr GraphQL endpoint URL |
|
|
679
|
+
| `PREPR_ENV` | Yes | - | Environment mode (`preview` or `production`) |
|
|
680
|
+
|
|
681
|
+
### Middleware Options
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
// Simple usage (creates new NextResponse)
|
|
685
|
+
createPreprMiddleware(request, {
|
|
686
|
+
preview: boolean // Enable preview mode functionality
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
// Chaining usage (uses existing NextResponse)
|
|
690
|
+
createPreprMiddleware(request, response, {
|
|
691
|
+
preview: boolean // Enable preview mode functionality
|
|
692
|
+
})
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### Toolbar Options
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
<PreprToolbarProvider
|
|
699
|
+
props={toolbarProps}
|
|
700
|
+
options={{
|
|
701
|
+
debug: true // Enable debug logging
|
|
702
|
+
}}
|
|
703
|
+
>
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
## 🚨 Troubleshooting
|
|
707
|
+
|
|
708
|
+
### Common Issues
|
|
709
|
+
|
|
710
|
+
#### Toolbar Not Showing
|
|
711
|
+
- **Check environment**: Ensure `PREPR_ENV=preview` is set
|
|
712
|
+
- **Verify GraphQL URL**: Make sure `PREPR_GRAPHQL_URL` is correct and follows the format `https://graphql.prepr.io/YOUR_ACCESS_TOKEN`
|
|
713
|
+
- **Check token permissions**: Ensure "Enable edit mode" is checked in Prepr
|
|
714
|
+
|
|
715
|
+
#### Headers Not Working
|
|
716
|
+
- **Middleware setup**: Verify middleware is properly configured
|
|
717
|
+
- **API calls**: Ensure you're using `getPreprHeaders()` in your API calls
|
|
718
|
+
- **Environment**: Check that environment variables are loaded
|
|
719
|
+
|
|
720
|
+
#### TypeScript Errors
|
|
721
|
+
- **Version compatibility**: Ensure you're using compatible versions of Next.js and React
|
|
722
|
+
- **Type imports**: Import types from `@preprio/prepr-nextjs/types`
|
|
723
|
+
|
|
724
|
+
#### Build Issues
|
|
725
|
+
- **CSS imports**: Make sure to import the CSS file in your layout
|
|
726
|
+
- **Server components**: Ensure server functions are only called in server components
|
|
727
|
+
|
|
728
|
+
### Error Handling
|
|
729
|
+
|
|
730
|
+
The package includes comprehensive error handling:
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
import { PreprError } from '@preprio/prepr-nextjs/server'
|
|
734
|
+
|
|
735
|
+
try {
|
|
736
|
+
const segments = await getPreprEnvironmentSegments(process.env.PREPR_GRAPHQL_URL!)
|
|
737
|
+
} catch (error) {
|
|
738
|
+
if (error instanceof PreprError) {
|
|
739
|
+
console.log('Error code:', error.code)
|
|
740
|
+
console.log('Context:', error.context)
|
|
741
|
+
}
|
|
147
742
|
}
|
|
148
743
|
```
|
|
149
744
|
|
|
150
|
-
|
|
745
|
+
### Debug Mode
|
|
746
|
+
|
|
747
|
+
Enable debug logging in development:
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
<PreprToolbarProvider
|
|
751
|
+
props={toolbarProps}
|
|
752
|
+
options={{ debug: true }}
|
|
753
|
+
>
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
## 📊 How It Works
|
|
757
|
+
|
|
758
|
+
### Middleware Functionality
|
|
759
|
+
|
|
760
|
+
The middleware automatically:
|
|
761
|
+
1. **Generates customer IDs**: Creates unique visitor identifiers
|
|
762
|
+
2. **Tracks UTM parameters**: Captures marketing campaign data
|
|
763
|
+
3. **Manages segments**: Handles audience segmentation
|
|
764
|
+
4. **Processes A/B tests**: Manages variant assignments
|
|
765
|
+
5. **Sets headers**: Adds necessary headers for API calls
|
|
766
|
+
|
|
767
|
+
### Toolbar Features
|
|
768
|
+
|
|
769
|
+
The toolbar provides:
|
|
770
|
+
- **Segment selection**: Switch between different audience segments
|
|
771
|
+
- **A/B testing**: Toggle between variants A and B
|
|
772
|
+
- **Edit mode**: Visual content editing capabilities
|
|
773
|
+
- **Reset functionality**: Clear personalization settings
|
|
774
|
+
|
|
775
|
+
### Visual Editing
|
|
151
776
|
|
|
152
|
-
|
|
777
|
+
When edit mode is enabled, the package:
|
|
778
|
+
1. **Scans content**: Identifies editable content using Stega encoding
|
|
779
|
+
2. **Highlights elements**: Shows proximity-based highlighting
|
|
780
|
+
3. **Provides overlays**: Click-to-edit functionality
|
|
781
|
+
4. **Syncs with Prepr**: Direct integration with Prepr's editing interface
|
|
153
782
|
|
|
154
|
-
|
|
155
|
-
The `getPreprUUID()` function will return the value of the `__prepr_uid` cookie. This can be useful if you want to store the `__prepr_uid` in a cookie or local storage.
|
|
783
|
+
## 🔄 Upgrading from v1 to v2
|
|
156
784
|
|
|
157
|
-
|
|
158
|
-
Returns the active segment from the `Prepr-Segments` header.
|
|
785
|
+
If you're upgrading from v1, please follow the [Upgrade Guide](./UPGRADE_GUIDE.md) for detailed migration instructions.
|
|
159
786
|
|
|
160
|
-
|
|
161
|
-
Returns the active variant from the `Prepr-ABTesting` header.
|
|
787
|
+
## 📜 License
|
|
162
788
|
|
|
163
|
-
|
|
164
|
-
Helper function to only get the preview headers.
|
|
789
|
+
MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
165
790
|
|
|
166
|
-
|
|
167
|
-
Helper function that will either return the customer id header or the preview headers depending on the `PREPR_ENV` environment variable.
|
|
791
|
+
## 🆘 Support
|
|
168
792
|
|
|
169
|
-
|
|
170
|
-
|
|
793
|
+
- **Documentation**: [Prepr Documentation](https://docs.prepr.io)
|
|
794
|
+
- **Issues**: [GitHub Issues](https://github.com/preprio/prepr-nextjs/issues)
|
|
795
|
+
- **Support**: [Prepr Support](https://prepr.io/support)
|