Skill 09 · Contentful Personalization
Subchapter 9.18
references/optimization-shared.mdMarkdown7 KBView on GitHub
The Experience API returns visitor-specific OptimizationData:
{
(profile, selectedOptimizations, changes);
}Stateful browser and mobile SDKs apply that data to reactive state. Stateless server SDKs return it
to the current request. Entry resolution then combines a fetched baseline Contentful entry with
selectedOptimizations and returns either the authored variant or the original entry.
An accepted event may have no data yet. Check the discriminated result before reading it:
const result = await runtime.page();
if (result.accepted && result.data) {
const { profile, selectedOptimizations, changes } = result.data;
}The application owns the contentful.js client. It can hand entries to the SDK in two supported
ways:
baselineEntry, or call
resolveOptimizedEntry(entry).contentful: { client }, then use an entryId API such as
<OptimizedEntry entryId>, useOptimizedEntry({ entryId }), or fetchOptimizedEntry(entryId).Never pass both baselineEntry and entryId to a discriminated entry API.
Managed configuration accepts the following shape. defaultQuery and cache are optional:
const contentfulConfig = {
client,
defaultQuery: { locale: 'en-US' },
cache: { maxEntries: 100, ttlMs: 300_000 },
};Managed queries merge the default query, per-entry query, SDK or request locale fallback, and
include: 10. The per-instance cache defaults to 100 entries for five minutes. Use
clearContentfulEntryCache() when application policy requires invalidation. Prefetch APIs require
explicit entry descriptors; they do not crawl linked entries.
Fetch one concrete locale. Do not use withAllLocales or CDA locale=*: locale-keyed field maps
cannot be resolved and fall back to baseline.
Fetch enough linked depth to include:
fields.nt_experiences links;fields.nt_variants links;The managed path supplies include: 10. A manual fetch must supply a comparable depth.
The resolved value is a base contentful Entry. Cast it to the application’s generated entry type
at the render boundary when required.
Resolution returns the baseline entry when no selection matches, links are unresolved, the entry is not optimized, or the payload is all-locale. This is a valid outcome and must render without an error state.
Do not use resolved.entry === baselineEntry to infer that no experience matched. A control
assignment has variantIndex: 0, returns the baseline entry, and still has a defined
selectedOptimization.
Resolution does not read consent. Consent controls event admission and therefore whether selections become available; missing selections naturally resolve to baseline.
Managed-fetch failure is different from baseline fallback: surface it through the runtime’s loading, error callback, and error fallback APIs.
The suite has two independent permissions:
consent: may admit personalization and analytics events;persistenceConsent: may retain profile continuity.A boolean sets both axes. In stateful Web, React, Next.js browser, and React Native runtimes, use the object form when policy differs:
sdk.consent({ events: true, persistence: false });Node binds the same decision per request with
optimization.forRequest({ consent: { events, persistence } }); it has no singleton consent()
method.
With no configured or persisted decision, state is undefined, not false. Web-family defaults
allow identify and page before explicit consent; React Native defaults allow identify and
screen. Tighten this with allowedEventTypes: [] when policy requires fail-closed behavior.
The application owns the consent record and policy. Stateful SDKs own their documented profile storage; the Node SDK stores nothing and makes persistence an application responsibility.
reset() clears profile and selection continuity but does not erase the application’s consent
record. Clear application-owned authentication and consent state separately when appropriate.
For the first personalized render:
Stateful React runtimes initialize after React commits, so loading and initialization failure are real states. Node is request-scoped. The imperative Web class is synchronously constructed but has no selections until an accepted Experience call returns.
Auto page or screen trackers deduplicate consecutive route keys. Mount one tracker per router tree. In a hybrid server/browser integration, designate one owner for the initial page event so hydration does not report it twice.
Entry re-resolution after load is opt-in. Configure it globally or per entry; a per-entry value overrides the global default. It reacts to browser or mobile profile, identity, consent, and preview state changes according to the selected runtime. Do not enable it merely to make the initial render work.
Merge tags are profile-backed substitutions, separate from entry replacement. Validate a Contentful
entry with the runtime’s isMergeTagEntry type guard before passing it to getMergeTagValue; the
configured fallback is returned when no profile value exists.
getFlag(name) reads a Custom Flag. Tracking semantics differ: stateful runtimes may emit a flag
view when consent and profile allow, while Node’s base read is side-effect free and requires an
explicit request-bound trackFlagView() for exposure reporting.
When an entry stays on baseline, check in order:
allowedEventTypes admit that event.nt_experiences and the matching experience carries nt_variants.When interactions are missing, also check the runtime’s tracking opt-outs, presence of a current profile, resolved entry ID, visibility or gesture threshold, and duplicate route/event suppression.