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