@contentful/optimization-node 0.1.0-alpha9 → 1.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 +247 -250
- package/THIRD_PARTY_NOTICES.txt +2096 -0
- package/dist/260.mjs +1 -1
- package/dist/260.mjs.map +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.cjs.map +1 -1
- package/dist/core-sdk.cjs +15 -2
- package/dist/core-sdk.cjs.map +1 -1
- package/dist/core-sdk.d.cts +13 -0
- package/dist/core-sdk.d.mts +13 -0
- package/dist/core-sdk.d.ts +13 -0
- package/dist/core-sdk.mjs +1 -0
- package/dist/index.cjs +24 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -16
- package/dist/index.d.mts +36 -16
- package/dist/index.d.ts +36 -16
- package/dist/index.mjs +23 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://www.contentful.com/developers/docs/personalization/">
|
|
3
|
-
<img alt="Contentful Logo" title="Contentful" src="
|
|
3
|
+
<img alt="Contentful Logo" title="Contentful" src="https://raw.githubusercontent.com/contentful/optimization/optimization-node-v1.0.0/contentful-icon.png" width="150">
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
@@ -10,314 +10,311 @@
|
|
|
10
10
|
|
|
11
11
|
<div align="center">
|
|
12
12
|
|
|
13
|
-
[
|
|
14
|
-
[Contributing](
|
|
13
|
+
[Guides](https://contentful.github.io/optimization/documents/Documentation.Guides.html) ·
|
|
14
|
+
[Reference](https://contentful.github.io/optimization) · [Contributing](https://github.com/contentful/optimization/blob/optimization-node-v1.0.0/CONTRIBUTING.md)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
> [!WARNING]
|
|
19
19
|
>
|
|
20
|
-
> The Optimization SDK Suite is pre-release (alpha). Breaking changes
|
|
20
|
+
> The Optimization SDK Suite is pre-release (alpha). Breaking changes can be published at any time.
|
|
21
21
|
|
|
22
|
-
The Optimization Node SDK implements
|
|
23
|
-
[Optimization Core
|
|
24
|
-
|
|
22
|
+
The Optimization Node SDK implements stateless server-side optimization behavior on top of the
|
|
23
|
+
[Optimization Core SDK](https://github.com/contentful/optimization/blob/optimization-node-v1.0.0/packages/universal/core-sdk/README.md). Use it for server rendering, server
|
|
24
|
+
functions, and Node services that need request-scoped profile evaluation or event emission.
|
|
25
|
+
|
|
26
|
+
If you are integrating a Node application, start with [Getting Started](#getting-started), then use
|
|
27
|
+
[Integrating the Optimization Node SDK in a Node app](https://contentful.github.io/optimization/documents/Documentation.Guides.integrating-the-node-sdk-in-a-node-app.html)
|
|
28
|
+
for the step-by-step flow. This README keeps the package orientation and common setup options close
|
|
29
|
+
at hand; generated [reference documentation](https://contentful.github.io/optimization) remains the
|
|
30
|
+
source of truth for exported API signatures.
|
|
25
31
|
|
|
26
32
|
<details>
|
|
27
33
|
<summary>Table of Contents</summary>
|
|
28
34
|
<!-- mtoc-start -->
|
|
29
35
|
|
|
30
|
-
- [Getting
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
|
|
38
|
-
- [
|
|
39
|
-
- [Personalization Data Resolution Methods](#personalization-data-resolution-methods)
|
|
40
|
-
- [`getCustomFlag`](#getcustomflag)
|
|
41
|
-
- [`personalizeEntry`](#personalizeentry)
|
|
42
|
-
- [`getMergeTagValue`](#getmergetagvalue)
|
|
43
|
-
- [Personalization and Analytics Event Methods](#personalization-and-analytics-event-methods)
|
|
44
|
-
- [`identify`](#identify)
|
|
45
|
-
- [`page`](#page)
|
|
46
|
-
- [`screen`](#screen)
|
|
47
|
-
- [`track`](#track)
|
|
48
|
-
- [`trackComponentView`](#trackcomponentview)
|
|
49
|
-
- [`trackComponentClick`](#trackcomponentclick)
|
|
50
|
-
- [`trackFlagView`](#trackflagview)
|
|
51
|
-
- [Interceptors](#interceptors)
|
|
52
|
-
- [Life-cycle Interceptors](#life-cycle-interceptors)
|
|
36
|
+
- [Getting started](#getting-started)
|
|
37
|
+
- [When to use this package](#when-to-use-this-package)
|
|
38
|
+
- [Common configuration](#common-configuration)
|
|
39
|
+
- [Core workflows](#core-workflows)
|
|
40
|
+
- [Request-scoped events](#request-scoped-events)
|
|
41
|
+
- [Content resolution](#content-resolution)
|
|
42
|
+
- [Caching guidance](#caching-guidance)
|
|
43
|
+
- [Development harness](#development-harness)
|
|
44
|
+
- [Related](#related)
|
|
53
45
|
|
|
54
46
|
<!-- mtoc-end -->
|
|
55
47
|
</details>
|
|
56
48
|
|
|
57
|
-
## Getting
|
|
49
|
+
## Getting started
|
|
58
50
|
|
|
59
51
|
Install using an NPM-compatible package manager, pnpm for example:
|
|
60
52
|
|
|
61
53
|
```sh
|
|
62
|
-
pnpm
|
|
54
|
+
pnpm add @contentful/optimization-node
|
|
63
55
|
```
|
|
64
56
|
|
|
57
|
+
Add `contentful` too when the SDK will use your app-owned `contentful.js` client for managed entry
|
|
58
|
+
fetching.
|
|
59
|
+
|
|
65
60
|
Import the Optimization class; both CJS and ESM module systems are supported, ESM preferred:
|
|
66
61
|
|
|
67
62
|
```ts
|
|
68
|
-
import
|
|
63
|
+
import ContentfulOptimization from '@contentful/optimization-node'
|
|
69
64
|
```
|
|
70
65
|
|
|
71
|
-
|
|
66
|
+
Create the SDK once per module or process, then bind consent and request context in each route:
|
|
72
67
|
|
|
73
68
|
```ts
|
|
74
|
-
const optimization = new
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
Reference implementations illustrate how the SDK may be used under common scenarios, as well as
|
|
80
|
-
select less-common scenarios, with the most basic example solution possible.
|
|
81
|
-
|
|
82
|
-
- [Node SSR Only](../../../implementations/node-ssr-only/README.md): Example application that uses
|
|
83
|
-
the Node SDK to render a personalized Web page
|
|
84
|
-
- [Node SSR + Web Vanilla](../../../implementations/node-ssr-web-vanilla/README.md): Example
|
|
85
|
-
application demonstrating simple profile synchronization between the Node and
|
|
86
|
-
[Web](../web/README.md) SDKs via cookie
|
|
87
|
-
|
|
88
|
-
## Configuration
|
|
89
|
-
|
|
90
|
-
### Top-level Configuration Options
|
|
91
|
-
|
|
92
|
-
| Option | Required? | Default | Description |
|
|
93
|
-
| ----------------- | --------- | ----------------------------- | ------------------------------------------------------------------------------ |
|
|
94
|
-
| `analytics` | No | See "Analytics Options" | Configuration specific to the Analytics/Insights API |
|
|
95
|
-
| `app` | No | `undefined` | The application definition used to attribute events to a specific consumer app |
|
|
96
|
-
| `clientId` | Yes | N/A | The Optimization API key |
|
|
97
|
-
| `environment` | No | `'main'` | The environment identifier |
|
|
98
|
-
| `eventBuilder` | No | See "Event Builder Options" | Event builder configuration (channel/library metadata, etc.) |
|
|
99
|
-
| `fetchOptions` | No | See "Fetch Options" | Configuration for Fetch timeout and retry functionality |
|
|
100
|
-
| `logLevel` | No | `'error'` | Minimum log level for the default console sink |
|
|
101
|
-
| `personalization` | No | See "Personalization Options" | Configuration specific to the Personalization/Experience API |
|
|
102
|
-
|
|
103
|
-
### Analytics Options
|
|
104
|
-
|
|
105
|
-
| Option | Required? | Default | Description |
|
|
106
|
-
| --------- | --------- | ------------------------------------------ | ----------------------------- |
|
|
107
|
-
| `baseUrl` | No | `'https://ingest.insights.ninetailed.co/'` | Base URL for the Insights API |
|
|
108
|
-
|
|
109
|
-
### Event Builder Options
|
|
110
|
-
|
|
111
|
-
Event builder options should only be supplied when building an SDK on top of the Optimization Node
|
|
112
|
-
SDK or any of its descendent SDKs.
|
|
113
|
-
|
|
114
|
-
| Option | Required? | Default | Description |
|
|
115
|
-
| --------- | --------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
116
|
-
| `channel` | No | `'server'` | The channel that identifies where events originate from (e.g. `'web'`, `'mobile'`) |
|
|
117
|
-
| `library` | No | `{ name: 'Optimization Node API', version: '0.0.0' }` | The client library metadata that is attached to all events |
|
|
118
|
-
|
|
119
|
-
### Fetch Options
|
|
120
|
-
|
|
121
|
-
Fetch options allow for configuration of a Fetch API-compatible fetch method and the retry/timeout
|
|
122
|
-
logic integrated into the Optimization API Client. Specify the `fetchMethod` when the host
|
|
123
|
-
application environment does not offer a `fetch` method that is compatible with the standard Fetch
|
|
124
|
-
API in its global scope.
|
|
125
|
-
|
|
126
|
-
| Option | Required? | Default | Description |
|
|
127
|
-
| ------------------ | --------- | ----------- | --------------------------------------------------------------------- |
|
|
128
|
-
| `fetchMethod` | No | `undefined` | Signature of a fetch method used by the API clients |
|
|
129
|
-
| `intervalTimeout` | No | `0` | Delay (in milliseconds) between retry attempts |
|
|
130
|
-
| `onFailedAttempt` | No | `undefined` | Callback invoked whenever a retry attempt fails |
|
|
131
|
-
| `onRequestTimeout` | No | `undefined` | Callback invoked when a request exceeds the configured timeout |
|
|
132
|
-
| `requestTimeout` | No | `3000` | Maximum time (in milliseconds) to wait for a response before aborting |
|
|
133
|
-
| `retries` | No | `1` | Maximum number of retry attempts |
|
|
134
|
-
|
|
135
|
-
Configuration method signatures:
|
|
136
|
-
|
|
137
|
-
- `fetchMethod`: `(url: string | URL, init: RequestInit) => Promise<Response>`
|
|
138
|
-
- `onFailedAttempt` and `onRequestTimeout`: `(options: FetchMethodCallbackOptions) => void`
|
|
139
|
-
|
|
140
|
-
### Personalization Options
|
|
141
|
-
|
|
142
|
-
| Option | Required? | Default | Description |
|
|
143
|
-
| ----------------- | --------- | ------------------------------------- | ------------------------------------------------------------------- |
|
|
144
|
-
| `baseUrl` | No | `'https://experience.ninetailed.co/'` | Base URL for the Experience API |
|
|
145
|
-
| `enabledFeatures` | No | `['ip-enrichment', 'location']` | Enabled features which the API may use for each request |
|
|
146
|
-
| `ip` | No | `undefined` | IP address to override the API behavior for IP analysis |
|
|
147
|
-
| `locale` | No | `'en-US'` (in API) | Locale used to translate `location.city` and `location.country` |
|
|
148
|
-
| `plainText` | No | `false` | Sends performance-critical endpoints in plain text |
|
|
149
|
-
| `preflight` | No | `false` | Instructs the API to aggregate a new profile state but not store it |
|
|
150
|
-
|
|
151
|
-
## Optimization Methods
|
|
152
|
-
|
|
153
|
-
Arguments marked with an asterisk (\*) are always required.
|
|
154
|
-
|
|
155
|
-
### Personalization Data Resolution Methods
|
|
156
|
-
|
|
157
|
-
#### `getCustomFlag`
|
|
158
|
-
|
|
159
|
-
Get the specified Custom Flag's value from the provided changes array.
|
|
160
|
-
|
|
161
|
-
Arguments:
|
|
162
|
-
|
|
163
|
-
- `name`\*: The name/key of the Custom Flag
|
|
164
|
-
- `changes`: Changes array
|
|
165
|
-
|
|
166
|
-
Returns:
|
|
167
|
-
|
|
168
|
-
- The resolved value for the specified Custom Flag, or `undefined` if it cannot be found.
|
|
169
|
-
|
|
170
|
-
> [!NOTE]
|
|
171
|
-
>
|
|
172
|
-
> If the `changes` argument is omitted, the method will return `undefined`.
|
|
173
|
-
|
|
174
|
-
#### `personalizeEntry`
|
|
175
|
-
|
|
176
|
-
Resolve a baseline Contentful entry to a personalized variant using the provided selected
|
|
177
|
-
personalizations.
|
|
178
|
-
|
|
179
|
-
Type arguments:
|
|
180
|
-
|
|
181
|
-
- `S`: Entry skeleton type
|
|
182
|
-
- `M`: Chain modifiers
|
|
183
|
-
- `L`: Locale code
|
|
184
|
-
|
|
185
|
-
Arguments:
|
|
186
|
-
|
|
187
|
-
- `entry`\*: The entry to personalize
|
|
188
|
-
- `personalizations`: Selected personalizations
|
|
189
|
-
|
|
190
|
-
Returns:
|
|
191
|
-
|
|
192
|
-
- The resolved personalized entry variant, or the supplied baseline entry if baseline is the
|
|
193
|
-
selected variant or a variant cannot be found.
|
|
194
|
-
|
|
195
|
-
> [!NOTE]
|
|
196
|
-
>
|
|
197
|
-
> If the `personalizations` argument is omitted, the method will return the baseline entry.
|
|
198
|
-
|
|
199
|
-
#### `getMergeTagValue`
|
|
200
|
-
|
|
201
|
-
Resolve a "Merge Tag" to a value based on the provided profile. A "Merge Tag" is a special Rich Text
|
|
202
|
-
fragment supported by Contentful that specifies a profile data member to be injected into the Rich
|
|
203
|
-
Text when rendered.
|
|
204
|
-
|
|
205
|
-
Arguments:
|
|
206
|
-
|
|
207
|
-
- `embeddedNodeEntryTarget`\*: The merge tag entry node to resolve
|
|
208
|
-
- `profile`: The user profile
|
|
209
|
-
|
|
210
|
-
> [!NOTE]
|
|
211
|
-
>
|
|
212
|
-
> If the `profile` argument is omitted, the method will return the merge tag's fallback value.
|
|
213
|
-
|
|
214
|
-
### Personalization and Analytics Event Methods
|
|
215
|
-
|
|
216
|
-
Only the following methods may return an `OptimizationData` object:
|
|
217
|
-
|
|
218
|
-
- `identify`
|
|
219
|
-
- `page`
|
|
220
|
-
- `screen`
|
|
221
|
-
- `track`
|
|
222
|
-
- `trackComponentView` (when `payload.sticky` is `true`)
|
|
223
|
-
|
|
224
|
-
`trackComponentClick` and `trackFlagView` return no data. When returned, `OptimizationData`
|
|
225
|
-
contains:
|
|
226
|
-
|
|
227
|
-
- `changes`: Currently used for Custom Flags
|
|
228
|
-
- `personalizations`: Selected personalizations for the profile
|
|
229
|
-
- `profile`: Profile associated with the evaluated events
|
|
230
|
-
|
|
231
|
-
#### `identify`
|
|
232
|
-
|
|
233
|
-
Identify the current profile/visitor to associate traits with a profile.
|
|
234
|
-
|
|
235
|
-
Arguments:
|
|
236
|
-
|
|
237
|
-
- `payload`\*: Identify event builder arguments object, including an optional `profile` property
|
|
238
|
-
with a `PartialProfile` value that requires only an `id`
|
|
239
|
-
|
|
240
|
-
#### `page`
|
|
241
|
-
|
|
242
|
-
Record a personalization page view.
|
|
243
|
-
|
|
244
|
-
Arguments:
|
|
69
|
+
const optimization = new ContentfulOptimization({
|
|
70
|
+
clientId: 'your-client-id',
|
|
71
|
+
environment: 'main',
|
|
72
|
+
locale: 'en-US',
|
|
73
|
+
})
|
|
245
74
|
|
|
246
|
-
|
|
247
|
-
|
|
75
|
+
function appPolicyAllowsOptimizationEvent(req: { cookies?: Record<string, string> }): boolean {
|
|
76
|
+
return req.cookies?.['app-personalization-consent'] === 'granted'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function renderRequest(
|
|
80
|
+
req: { cookies?: Record<string, string>; headers: { 'accept-language'?: string } },
|
|
81
|
+
profileId: string,
|
|
82
|
+
) {
|
|
83
|
+
const appLocale = getAppLocale(req)
|
|
84
|
+
const requestOptimization = optimization.forRequest({
|
|
85
|
+
consent: {
|
|
86
|
+
events: appPolicyAllowsOptimizationEvent(req),
|
|
87
|
+
persistence: appPolicyAllowsOptimizationEvent(req),
|
|
88
|
+
},
|
|
89
|
+
locale: appLocale,
|
|
90
|
+
eventContext: { locale: appLocale },
|
|
91
|
+
profile: { id: profileId },
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const { accepted, data } = await requestOptimization.page()
|
|
95
|
+
|
|
96
|
+
return accepted ? data : undefined
|
|
97
|
+
}
|
|
98
|
+
```
|
|
248
99
|
|
|
249
|
-
|
|
100
|
+
## When to use this package
|
|
250
101
|
|
|
251
|
-
|
|
102
|
+
Use `@contentful/optimization-node` for server-side rendering, server functions, and Node services
|
|
103
|
+
that need stateless profile evaluation or event emission. Use the Web or React Web SDK alongside it
|
|
104
|
+
when browser-side consent, anonymous ID persistence, automatic interaction tracking, or live updates
|
|
105
|
+
are part of the same application.
|
|
252
106
|
|
|
253
|
-
|
|
107
|
+
## Common configuration
|
|
254
108
|
|
|
255
|
-
|
|
256
|
-
|
|
109
|
+
The Node SDK is stateless. It does not maintain consent, profile, or browser persistence state
|
|
110
|
+
between requests. For cross-SDK consent guidance, see
|
|
111
|
+
[Consent management in the Optimization SDK Suite](https://github.com/contentful/optimization/blob/optimization-node-v1.0.0/documentation/concepts/consent-management-in-the-optimization-sdk-suite.md).
|
|
257
112
|
|
|
258
|
-
|
|
113
|
+
| Option | Required? | Default | Description |
|
|
114
|
+
| ------------------- | --------- | ---------------------- | ----------------------------------------------------------- |
|
|
115
|
+
| `clientId` | Yes | N/A | Shared API key for Experience API and Insights API requests |
|
|
116
|
+
| `environment` | No | `'main'` | Contentful environment identifier |
|
|
117
|
+
| `api` | No | See API options below | Experience API and Insights API endpoint options |
|
|
118
|
+
| `app` | No | `undefined` | Application metadata attached to outgoing event context |
|
|
119
|
+
| `contentful` | No | `undefined` | App-owned `contentful.js` client, default query, and cache |
|
|
120
|
+
| `locale` | No | `undefined` | Default SDK Experience API and event locale |
|
|
121
|
+
| `fetchOptions` | No | SDK defaults | Fetch timeout and retry behavior |
|
|
122
|
+
| `allowedEventTypes` | No | `['identify', 'page']` | Event types allowed before request event consent is granted |
|
|
123
|
+
| `eventBuilder` | No | Node SDK defaults | Event metadata overrides for SDK-layer authors |
|
|
124
|
+
| `logLevel` | No | `'error'` | Minimum log level for the default console sink |
|
|
259
125
|
|
|
260
|
-
|
|
126
|
+
Common `api` options:
|
|
261
127
|
|
|
262
|
-
|
|
128
|
+
| Option | Required? | Default | Description |
|
|
129
|
+
| ------------------- | --------- | ------------------------------------------ | ------------------------------------------------ |
|
|
130
|
+
| `experienceBaseUrl` | No | `'https://experience.ninetailed.co/'` | Base URL for the Experience API |
|
|
131
|
+
| `insightsBaseUrl` | No | `'https://ingest.insights.ninetailed.co/'` | Base URL for the Insights API |
|
|
132
|
+
| `enabledFeatures` | No | `['ip-enrichment', 'location']` | Experience API features to apply to each request |
|
|
263
133
|
|
|
264
|
-
-
|
|
265
|
-
|
|
134
|
+
Request-scoped Experience options belong in `experienceOptions` when creating the request-bound
|
|
135
|
+
client:
|
|
266
136
|
|
|
267
|
-
|
|
137
|
+
| Option | Description |
|
|
138
|
+
| ----------- | ------------------------------------------------------------- |
|
|
139
|
+
| `ip` | IP address override used by the Experience API |
|
|
140
|
+
| `locale` | Locale query parameter for localized Experience API responses |
|
|
141
|
+
| `plainText` | Sends performance-critical Experience API endpoints as text |
|
|
142
|
+
| `preflight` | Aggregates a new profile state without storing it |
|
|
268
143
|
|
|
269
|
-
|
|
270
|
-
additional personalization component view is recorded. This method only returns `OptimizationData`
|
|
271
|
-
when the component is marked as "sticky".
|
|
144
|
+
Request-scoped Insights options belong in `insightsOptions`:
|
|
272
145
|
|
|
273
|
-
|
|
146
|
+
| Option | Description |
|
|
147
|
+
| -------- | ----------------------------------------------------------------- |
|
|
148
|
+
| `beacon` | Last-chance sender for serialized Insights API batches if needed. |
|
|
274
149
|
|
|
275
|
-
- `
|
|
276
|
-
|
|
150
|
+
Use the request-scoped top-level `locale` on `forRequest()` as the promoted path for localized
|
|
151
|
+
Experience API responses and default event context. `experienceOptions.locale` remains available as
|
|
152
|
+
an advanced low-level pass-through when `locale` is not supplied. If both are provided, request
|
|
153
|
+
`locale` wins.
|
|
277
154
|
|
|
278
|
-
|
|
155
|
+
Common `fetchOptions` are `fetchMethod`, `requestTimeout`, `retries`, `intervalTimeout`,
|
|
156
|
+
`onFailedAttempt`, and `onRequestTimeout`. Default retries intentionally apply only to HTTP `503`
|
|
157
|
+
responses.
|
|
279
158
|
|
|
280
|
-
|
|
159
|
+
Choose the application Contentful locale in your router, i18n layer, or request policy. Pass that
|
|
160
|
+
value directly to Contentful CDA requests, and pass the same value to
|
|
161
|
+
`forRequest({ locale: appLocale })` when Experience API responses and event context should use the
|
|
162
|
+
same language. Merge tags that reference localized profile fields such as `location.city` and
|
|
163
|
+
`location.country` then resolve in a language consistent with the rendered content. See
|
|
164
|
+
[Locale handling in the Optimization SDK Suite](https://contentful.github.io/optimization/documents/Documentation.Concepts.Locale_handling_in_the_Optimization_SDK_Suite.html)
|
|
165
|
+
for the full locale model.
|
|
281
166
|
|
|
282
|
-
|
|
167
|
+
For every option, callback payload, and exported type, use the generated
|
|
168
|
+
[Node SDK reference](https://contentful.github.io/optimization/modules/_contentful_optimization-node.html).
|
|
283
169
|
|
|
284
|
-
|
|
170
|
+
## Core workflows
|
|
285
171
|
|
|
286
|
-
|
|
172
|
+
### Request-scoped events
|
|
287
173
|
|
|
288
|
-
|
|
174
|
+
Build event context from the incoming request, bind application-owned consent state with
|
|
175
|
+
`forRequest()`, then call `page()`, `identify()`, `screen()`, `track()`, or sticky `trackView()` on
|
|
176
|
+
the returned request object:
|
|
289
177
|
|
|
290
|
-
|
|
178
|
+
```ts
|
|
179
|
+
import type { Request } from 'express'
|
|
180
|
+
|
|
181
|
+
app.get('/products/:slug', async (req, res) => {
|
|
182
|
+
const appLocale = getAppLocale(req)
|
|
183
|
+
const requestOptimization = optimization.forRequest({
|
|
184
|
+
consent: {
|
|
185
|
+
events: appPolicyAllowsOptimizationEvent(req),
|
|
186
|
+
persistence: appPolicyAllowsOptimizationEvent(req),
|
|
187
|
+
},
|
|
188
|
+
locale: appLocale,
|
|
189
|
+
eventContext: {
|
|
190
|
+
locale: appLocale,
|
|
191
|
+
},
|
|
192
|
+
profile: { id: req.cookies.profileId },
|
|
193
|
+
})
|
|
194
|
+
const { accepted, data: optimizationData } = await requestOptimization.page({
|
|
195
|
+
properties: { path: req.path },
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
if (accepted && requestOptimization.canPersistProfile && optimizationData?.profile.id) {
|
|
199
|
+
persistProfileId(res, optimizationData.profile.id)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
res.render('product', { optimizationData })
|
|
203
|
+
})
|
|
204
|
+
```
|
|
291
205
|
|
|
292
|
-
|
|
293
|
-
|
|
206
|
+
For default-on application policies that do not render an end-user consent UI, replace the
|
|
207
|
+
request-specific consent lookup with accepted consent:
|
|
294
208
|
|
|
295
|
-
|
|
209
|
+
```ts
|
|
210
|
+
const requestOptimization = optimization.forRequest({
|
|
211
|
+
consent: { events: true, persistence: true },
|
|
212
|
+
locale: appLocale,
|
|
213
|
+
eventContext: { locale: appLocale },
|
|
214
|
+
profile,
|
|
215
|
+
})
|
|
216
|
+
```
|
|
296
217
|
|
|
297
|
-
- `
|
|
218
|
+
Node SDK event calls fail closed except for the default pre-consent allowlist, `identify` and
|
|
219
|
+
`page`. Those allowlisted events are sent with `context.gdpr.isConsentGiven: false` when request
|
|
220
|
+
event consent is not granted. Pass `allowedEventTypes: []` to require strict opt-in for all
|
|
221
|
+
stateless event methods.
|
|
298
222
|
|
|
299
|
-
|
|
223
|
+
In stateless runtimes, Insights-backed methods require a request-bound profile for delivery.
|
|
224
|
+
Non-sticky `trackView`, `trackClick`, `trackHover`, and `trackFlagView` require a profile ID passed
|
|
225
|
+
to `forRequest()`.
|
|
300
226
|
|
|
301
|
-
|
|
227
|
+
### Content resolution
|
|
302
228
|
|
|
303
|
-
|
|
229
|
+
When a `contentful.js` client is available, prefer SDK-managed fetching by entry ID. Configure the
|
|
230
|
+
client once, then call `fetchOptimizedEntry()` on a request-bound client after an accepted
|
|
231
|
+
Experience API call. `forRequest()` clients use the latest selected optimizations when omitted;
|
|
232
|
+
singleton calls require explicit `selectedOptimizations`.
|
|
304
233
|
|
|
305
|
-
|
|
234
|
+
```ts
|
|
235
|
+
const optimization = new ContentfulOptimization({
|
|
236
|
+
clientId: 'client-id',
|
|
237
|
+
contentful: { client: contentfulClient },
|
|
238
|
+
environment: 'main',
|
|
239
|
+
locale: appLocale,
|
|
240
|
+
})
|
|
306
241
|
|
|
307
|
-
|
|
242
|
+
const requestOptimization = optimization.forRequest({ consent: true, profile })
|
|
243
|
+
await requestOptimization.page()
|
|
244
|
+
const { baselineEntry, entry } = await requestOptimization.fetchOptimizedEntry('hero-entry')
|
|
245
|
+
```
|
|
308
246
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
247
|
+
Use `fetchContentfulEntries()` or `prefetchManagedEntries()` when a route knows several managed
|
|
248
|
+
entry IDs. Entries with the same normalized query share one `getEntries()` call; same-tick
|
|
249
|
+
single-entry calls can join that batch. Large `getEntries()` fetches are split into 100-ID chunks.
|
|
312
250
|
|
|
313
|
-
|
|
251
|
+
If your application already fetched the baseline entry, keep using the manual resolver:
|
|
314
252
|
|
|
315
253
|
```ts
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
254
|
+
const pageResult = await requestOptimization.page()
|
|
255
|
+
const resolvedEntry = optimization.resolveOptimizedEntry(
|
|
256
|
+
baselineEntry,
|
|
257
|
+
pageResult.accepted ? pageResult.data.selectedOptimizations : undefined,
|
|
258
|
+
)
|
|
319
259
|
```
|
|
320
260
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
261
|
+
Use one CDA locale in either path. For localized apps, configure your application locale. A
|
|
262
|
+
request-bound client uses `forRequest({ locale: appLocale })` as the managed Contentful query locale
|
|
263
|
+
when neither `contentful.defaultQuery` nor the per-call query sets `locale`. Pass the same value to
|
|
264
|
+
app-owned Contentful CDA requests and to `forRequest({ locale: appLocale })` when MergeTags that
|
|
265
|
+
read localized profile fields match the rendered entry language. Do not pass all-locale CDA
|
|
266
|
+
responses from `withAllLocales` or `locale=*`; the resolver expects direct single-locale field
|
|
267
|
+
values. See
|
|
268
|
+
[Entry personalization and variant resolution](https://contentful.github.io/optimization/documents/Documentation.Concepts.Entry_personalization_and_variant_resolution.html#single-locale-cda-entry-contract)
|
|
269
|
+
for the entry contract and
|
|
270
|
+
[Locale handling in the Optimization SDK Suite](https://contentful.github.io/optimization/documents/Documentation.Concepts.Locale_handling_in_the_Optimization_SDK_Suite.html)
|
|
271
|
+
for request locale behavior.
|
|
272
|
+
|
|
273
|
+
Use `getMergeTagValue()` for Contentful Rich Text merge tags and `getFlag()` for Custom Flags. If a
|
|
274
|
+
merge tag references localized profile fields such as `location.city` or `location.country`, its
|
|
275
|
+
resolved value follows the localized profile data returned by the Experience API. The Node SDK is
|
|
276
|
+
stateless, so `getFlag()` does not automatically emit flag-view tracking.
|
|
277
|
+
|
|
278
|
+
### Caching guidance
|
|
279
|
+
|
|
280
|
+
Cache raw Contentful delivery payloads in your application layer or with the SDK-managed entry
|
|
281
|
+
cache, not profile-evaluated SDK event results. Use `clearContentfulEntryCache()` when an SDK
|
|
282
|
+
instance must drop cached CDA entries.
|
|
283
|
+
|
|
284
|
+
| Data or call | Cache across requests? | Reason |
|
|
285
|
+
| --------------------------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------- |
|
|
286
|
+
| Raw Contentful entries fetched from CDA or the SDK-managed entry cache | Yes | They are content snapshots and can be resolved per request |
|
|
287
|
+
| `resolveOptimizedEntry()` and `getMergeTagValue()` results | Request-local only | Results depend on the current profile and optimization data |
|
|
288
|
+
| `page()`, `identify()`, `screen()`, `track()`, and sticky `trackView()` | No | These calls perform side effects and return request-specific profile |
|
|
289
|
+
| Non-sticky `trackView()`, `trackClick()`, `trackHover()`, `trackFlagView()` | No | These calls emit Insights API events |
|
|
290
|
+
|
|
291
|
+
The
|
|
292
|
+
[Node SDK integration guide](https://contentful.github.io/optimization/documents/Documentation.Guides.integrating-the-node-sdk-in-a-node-app.html)
|
|
293
|
+
covers request context, profile persistence, Contentful entry resolution, and hybrid Node + browser
|
|
294
|
+
setups in detail.
|
|
295
|
+
|
|
296
|
+
## Development harness
|
|
297
|
+
|
|
298
|
+
The package-local dev harness runs from `packages/node/node-sdk/dev/` and reads `.env` from this
|
|
299
|
+
package directory.
|
|
300
|
+
|
|
301
|
+
1. Start from `.env.example` and create or update `packages/node/node-sdk/.env`.
|
|
302
|
+
2. Prefer the repo-standard `PUBLIC_...` variable names shown in `.env.example`.
|
|
303
|
+
3. Start the harness from the repo root:
|
|
304
|
+
|
|
305
|
+
```sh
|
|
306
|
+
pnpm --filter @contentful/optimization-node dev
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Related
|
|
310
|
+
|
|
311
|
+
- [Integrating the Optimization Node SDK in a Node app](https://contentful.github.io/optimization/documents/Documentation.Guides.integrating-the-node-sdk-in-a-node-app.html) -
|
|
312
|
+
step-by-step server-side integration guide
|
|
313
|
+
- [Node SDK generated reference](https://contentful.github.io/optimization/modules/_contentful_optimization-node.html) -
|
|
314
|
+
exported API reference
|
|
315
|
+
- [Node reference implementation](https://github.com/contentful/optimization/blob/optimization-node-v1.0.0/implementations/node-sdk/README.md) - server-rendered
|
|
316
|
+
implementation using request-scoped SDK calls
|
|
317
|
+
- [Node + Web reference implementation](https://github.com/contentful/optimization/blob/optimization-node-v1.0.0/implementations/node-sdk+web-sdk/README.md) -
|
|
318
|
+
hybrid SSR and browser implementation
|
|
319
|
+
- [Optimization Web SDK](https://github.com/contentful/optimization/blob/optimization-node-v1.0.0/packages/web/web-sdk/README.md) - browser SDK used when the same application
|
|
320
|
+
also needs client-side consent, persistence, tracking, or live updates
|