Use this document to keep the application Contentful locale separate from the SDK Experience/event locale across Web, React Web, Next.js, Node, React Native, iOS, and Android applications. For app-owned content fetching and entry resolution, the SDKs do not resolve Contentful locales, create Contentful Delivery API clients, or infer browser, device, or request locales. Applications choose their own locale from routing, i18n, native state, or request logic and pass it to manual Contentful calls or SDK-managed entry fetching. Preview and debug tooling is separate: preview-panel APIs can use Contentful clients or pre-fetched entries to load Optimization definitions, but they do not choose or fetch locales for application content.
For entry replacement mechanics, see Entry optimization and variant resolution. For package setup, use the relevant integration guide or package README.
Each runtime exposes the SDK Experience/event locale through its own API surface:
| Runtime | Locale API surface |
|---|---|
| Web | new ContentfulOptimization({ locale }), optimization.locale, optimization.states.locale, optimization.setLocale(locale), and <ctfl-optimization-root locale="..."> |
| React Web | <OptimizationRoot locale>, provider-owned <OptimizationProvider locale>, and useOptimization() access to sdk.locale, sdk.states.locale, and sdk.setLocale(locale) |
| Next.js | Preferred App Router createNextjsAppRouterOptimization({ locale }) bound components, Pages Router getServerSideOptimizationProps(context), lower-level server createNextjsOptimization({ locale }), request-scoped getNextjsServerOptimizationData(sdk, { locale }), ESR getNextjsEsrOptimizationData(sdk, { locale }), and manual client OptimizationRoot locale |
| Node | new ContentfulOptimization({ locale }) for a default, optimization.forRequest({ locale }) for request scope, and experienceOptions.locale as an advanced pass-through when request locale is absent |
| React Native | <OptimizationRoot locale>, provider-owned <OptimizationProvider locale>, ContentfulOptimization.create({ locale }), sdk.locale, and sdk.setLocale(locale) |
| iOS | OptimizationConfig(locale:), OptimizationRoot(config:), OptimizationClient.locale, and OptimizationClient.setLocale(_:) |
| Android | OptimizationConfig(locale = ...), Compose OptimizationRoot(config = ...), XML Views OptimizationManager.initialize(config = ...), OptimizationClient.locale, and OptimizationClient.setLocale(locale) |
Locale handling in an optimized application has two application-facing channels.
| Channel | Owned by | Used for |
|---|---|---|
| Application Contentful locale | Application router, i18n layer, request logic, or native app state | Contentful Delivery API (CDA) and Contentful Preview API (CPA) locale query values, UI language, route language, cache keys, and content refetching |
| SDK Experience/event locale | Optimization SDK configuration or request options | Experience API locale query values and default event context.locale |
The same string is often used for both channels, but the SDK treats them as separate inputs. The SDK does not know which Contentful locales are enabled in a space and does not validate that an SDK locale is supported by Contentful.
Choose an appLocale with application-owned logic, then pass it to CDA or CPA calls. JavaScript
managed fetching uses the application-owned contentful.js client from contentful: { client };
the SDK does not create clients, discover Contentful locales, infer browser or request locales, or
own locale policy.
SDK-managed Contentful fetch, JavaScript runtimes (TypeScript):
const appLocale = getAppLocale()
const optimization = new ContentfulOptimization({
clientId,
contentful: {
client: contentfulClient,
defaultQuery: { locale: appLocale },
},
locale: appLocale,
})
const entry = await optimization.fetchContentfulEntry(entryId, {
locale: appLocale,
})
Per-call entryQuery or query values override contentful.defaultQuery. If no Contentful query
locale is provided, managed fetching falls back to the SDK locale before calling
contentful.js getEntry(). Request-bound Node clients use forRequest({ locale }) as that
fallback. Use a concrete locale such as en-US; do not use withAllLocales or locale=* for
entries that the SDK will resolve.
Manual Contentful fetch, JavaScript runtimes (TypeScript):
const entry = await contentfulClient.getEntry(entryId, {
include: 10,
locale: appLocale,
})
Pass the same appLocale anywhere Contentful content is fetched: browser data loaders, React hooks,
server routes, React Native services, and native app content clients. If the app omits locale,
Contentful uses the space default locale.
Use the same appLocale in cache keys when localized content can differ.
JavaScript runtimes (TypeScript):
const cacheKey = `${appLocale}:${entryId}`
Stateful SDKs accept top-level locale as the default SDK Experience/event locale:
Web runtime (TypeScript):
const appLocale = getAppLocale()
const optimization = new ContentfulOptimization({
clientId,
locale: appLocale,
})
That value initializes the SDK locale state, sets the default Experience API request locale, and
provides the default event context locale. If locale is omitted, the Experience API locale query
is omitted by default. Event payloads still include context.locale: 'en-US' when neither an SDK
locale nor an event payload locale is available, because the event schemas require a locale.
The Experience API locale is not a Contentful CDA locale. It can localize Experience API profile
fields, such as location.city and location.country values used by MergeTags, and the SDK also
copies it into the default event context.locale. It does not localize Contentful entries or
resolve Contentful locale fallbacks.
The low-level Experience API client still supports its own default and per-request locale options.
Use those when calling the low-level API client directly or when an advanced stateless request needs
a pass-through.
Web, React Web, React Native, iOS, and Android keep live locale state:
optimization.locale, optimization.states.locale, and
optimization.setLocale(locale).<ctfl-optimization-root locale="..."> to the SDK instance that the
element creates and owns. If the element reuses an assigned sdk property or the global
window.contentfulOptimization instance, update that SDK instance with setLocale().locale prop
changes.OptimizationClient.locale and OptimizationClient.setLocale(_:).OptimizationClient.locale and OptimizationClient.setLocale(locale).On iOS and Android, call setLocale only after the client has initialized; set the initial locale
through OptimizationConfig before mounting or initializing.
setLocale(locale) validates and normalizes the SDK Experience/event locale. It does not refetch
Contentful content, update routes, or clear application caches. JavaScript managed fetching can use
the SDK locale only as the fallback getEntry() query locale when neither contentful.defaultQuery
nor a per-call query provides one. Application code must refetch Contentful entries with its chosen
Contentful locale.
Web or React Web client runtime (TypeScript):
const nextLocale = getAppLocaleFromRoute()
optimization.setLocale(nextLocale)
const entry = await contentfulClient.getEntry(entryId, {
include: 10,
locale: nextLocale,
})
Next.js composes the stateless Node SDK on the server with the React Web SDK on the client. For App
Router integrations, prefer the /app-router createNextjsAppRouterOptimization({ locale })
factory. It returns app-local bound OptimizationRoot, OptimizationProvider, OptimizedEntry,
and route trackers. Next.js resolves those bound exports to the automatic server implementation in
Server Components and to React Web-backed client exports in Client Components.
Next.js App Router binding module (TypeScript):
export const { NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } =
createNextjsAppRouterOptimization({
clientId,
locale: appLocale,
server: {
enabled: true,
consent,
},
})
The bound server root and bound server OptimizedEntry use the factory locale when they load
server Optimization data. The bound client root applies the same locale through React Web
configuration.
Keep lower-level/manual surfaces for direct request control. createNextjsOptimization({ locale })
sets the server SDK default locale, getNextjsServerOptimizationData(sdk, { locale }) binds an App
Router Server Component request, getServerSideOptimizationProps(context) binds a Pages Router
getServerSideProps request with explicit locale taking precedence over context.locale,
getNextjsEsrOptimizationData(sdk, { locale }) binds a request-rendered ESR flow, and manual client
OptimizationRoot locale follows the React Web behavior.
Next.js manual server runtime (TypeScript):
const optimization = createNextjsOptimization({
clientId,
locale: defaultLocale,
})
const { data } = await getNextjsServerOptimizationData(optimization, {
consent,
cookies,
headers,
locale: appLocale,
})
export const proxy = createNextjsOptimizationContextHandler()
Use the request-scoped manual locale path when a manual route can serve different locales. A
module-level createNextjsOptimization({ locale }) value is a default for the server SDK instance,
not the current request locale. Manual Server Components pass headers() to
getNextjsServerOptimizationData() so the SDK can derive page context from the request URL captured
by the Next.js proxy or middleware helper.
Locale handoff is separate from server optimization state handoff. When the browser provider has the
server data at its boundary, pass it with serverOptimizationState on OptimizationRoot. With App
Router bound components, the bound server root handles that state handoff. With Pages Router,
pages/_app.tsx receives the helper result through pageProps and passes it to the bound root.
Keep defaults for configuration or default state such as consent policy, not for server-returned
profile, selected optimizations, or changes.
Node and other stateless environments can set a constructor locale, but that value is a default
for the SDK instance. Bind request-specific locale with forRequest({ locale }), which is the
promoted request-scoped path for localized Experience API responses and default event context.
Node server runtime (TypeScript):
const appLocale = getAppLocale(request)
const requestUserAgent = getRequestUserAgent(request)
const requestOptimization = optimization.forRequest({
consent: true,
locale: appLocale,
eventContext: {
page: getPageContext(request),
userAgent: requestUserAgent ?? 'server',
},
})
const [entry, data] = await Promise.all([
contentfulClient.getEntry(entryId, { include: 10, locale: appLocale }),
requestOptimization.page(),
])
forRequest({ locale }) sets the request-bound Experience API locale and default event context
locale. If both locale and experienceOptions.locale are supplied, locale wins. Use
experienceOptions.locale only as an advanced low-level pass-through when locale is not supplied.
When a Node SDK is configured with contentful: { client }, root fetchOptimizedEntry(entryId)
needs explicit selectedOptimizations for personalized results. A request-bound forRequest()
client uses the latest accepted Experience response selections by default when
fetchOptimizedEntry(entryId) omits selectedOptimizations. It also uses the request locale as
the managed Contentful query locale when neither contentful.defaultQuery nor the per-call query
sets locale.
Entry resolution expects one localized view of a baseline entry and linked optimization entries. Pass direct single-locale field values to the runtime-specific entry resolution surface:
fetchContentfulEntry() and fetchOptimizedEntry() for JavaScript
SDK-managed fetching through an app-owned contentful.js client.resolveOptimizedEntry() for manual baseline entries.OptimizedEntry and useOptimizedEntry() with either baselineEntry or managed
entryId plus optional entryQuery.ctfl-optimized-entry with either baselineEntry or managed entry-id/entryId
plus optional entryQuery.OptimizedEntry from createNextjsAppRouterOptimization() for App Router Server
and Client Components, or from createNextjsPagesRouterOptimization() for Pages Router client
rendering. Lower-level server flows can use resolveOptimizedEntry() or managed
fetchOptimizedEntry(), then pass manual baselineEntry and resolvedData props or the managed
result to ServerOptimizedEntry when server-rendered tracking attributes are needed.OptimizedEntry and useOptimizedEntry() with either baselineEntry or managed
entryId plus optional entryQuery; useEntryResolver() remains manual-only.OptimizationClient.resolveOptimizedEntry(baseline:selectedOptimizations:) and SwiftUI
OptimizedEntry(entry:).OptimizationClient.resolveOptimizedEntry(...), Compose OptimizedEntry(entry:), and XML
Views OptimizedEntryView.setEntry(...).Do not pass all-locale CDA responses from withAllLocales or locale=*.
The SDK does not mutate application Contentful clients or infer when a content refetch is needed. When route or language state changes, the application must update SDK locale state, refetch Contentful content with the app locale, clear SDK-managed Contentful entry cache entries when those cached CDA results are no longer valid, and invalidate app caches as needed.
Applications own:
contentful.js
fetching.locale, provider locale, Next.js
createNextjsAppRouterOptimization({ locale }), Pages Router getServerSideOptimizationProps(),
lower-level Next.js getNextjsServerOptimizationData({ locale }), Next.js ESR
getNextjsEsrOptimizationData({ locale }), native config locale, native setLocale, or Node
forRequest({ locale }).