Skip to main content

Multi-country configuration

Archibald can serve several countries from a single build. Country-specific overrides live under a top-level countries map, keyed by country code, and are deep-merged on top of the base configuration when a country is active. Arrays are replaced, not concatenated, so a country can fully redefine a list.

// environment/common.ts
{
app: { features: ['wishlist', 'store-finder'] },
countries: {
AT: { app: { features: ['wishlist'] } }, // replaces the array
DE: { app: { baseSite: 'shop-de' } }
}
}

Multi-country resolution only runs when the multiCountry flag is enabled in the Archibald config. With the flag off, the base configuration is returned as-is.

On the native (React Native) client

On native, configuration is served by NativeConfig. Two hooks drive country resolution:

  • calculateCountry() — override it to return the active country code (the shop template reads it from local storage). It is consulted on every configuration read, so keep it cheap (an in-memory lookup).
  • Switching country — replace the whole config instance rather than mutating the active one. The shop template does this via setCountryNativeConfig, which persists the new country and calls setConfig(new ProjectNativeConfig()).
export class ProjectNativeConfig extends NativeConfig<ProjectConfig> {
override calculateCountry() {
return nativeStorage.get('country');
}
}

export async function setCountryNativeConfig(country: Countries | null) {
await nativeStorage.set('country', country);
setConfig(new ProjectNativeConfig()); // swap the instance, don't mutate
}

Performance: the merged config is memoized

Every configuration read (get, getAll, isSet, …) resolves the active country configuration. Because the merged result is invariant for the lifetime of a NativeConfig instance — the raw config is set once and switching country replaces the instance — NativeConfig memoizes the country-merged config instead of deep-merging on every read.

The cache is keyed by the resolved country, so:

  • Reading configuration many times per render performs the merge once, not once per read. (This previously showed up as hundreds of deep merges per render and noticeable jank on Android.)
  • A change in the value returned by calculateCountry() recomputes the merge once. This also covers the case where the country is loaded asynchronously: early reads resolve against the not-yet-loaded value and the config is recomputed once the country becomes available.
  • set() and replace() mutate the raw config and therefore clear the cache.
note

While the cache is warm, the object returned by config / getAll() is a stable reference rather than a fresh copy on each read. Treat configuration as read-only and route changes through set() / replace(); do not mutate the returned object in place.