Recipe

Personalization engine

Build a context-aware personalization pipeline that adapts content, pricing, and feature surfaces based on user signals collected during the session.

Overview

The personalization engine ingests a structured user-context object embedded in the prompt and uses it to drive dynamic content selection, tier-appropriate pricing displays, and feature gating. The engine runs entirely client-side after the initial server render, keeping latency near zero for the first paint while still delivering tailored experiences.

User context shape

The context object arrives as a serialized JSON blob injected into a <script id="__NIMBUS_CTX__"> tag during SSR. The schema is intentionally flat to keep hydration predictable:

{
  "tier": "pro" | "team" | "enterprise",
  "features": ["analytics", "export", "api"],
  "region": "us-east",
  "locale": "en-US",
  "experiments": ["pricing_v2", "onboarding_redesign"],
  "sessionAge": 142
}

Pipeline stages

  1. 1

    Context hydration

    On mount, read the DOM node, parse JSON, and store in a module-scoped reactive store. Fall back to a safe default (tier: "free") if the node is missing or malformed.

  2. 2

    Feature resolution

    Map the features array against a static capability matrix. Each feature key gates a UI subtree; missing keys render nothing. Experiment flags toggle variant branches.

  3. 3

    Content selection

    Use tier + region + locale to pick the correct copy bundle. Bundles are static imports tree-shaken at build time — no runtime fetch waterfall.

  4. 4

    Pricing surface

    Tier determines which price card is highlighted. Experiments may override the displayed currency or interval (monthly vs annual default).

Edge cases

  • Stale context: If sessionAge exceeds a configurable TTL (default 30 min), re-hydrate from a lightweight profile endpoint before rendering personalized content.
  • Missing experiments: Absent keys mean the control variant. Never throw — the engine must degrade gracefully.
  • Cross-region pricing: Region drives currency formatting via Intl.NumberFormat. Locale drives date and number separators independently.

Performance notes

The entire pipeline executes synchronously inside a single requestAnimationFrame callback. Content bundles are code-split per locale and lazy-loaded only when the context signals a non-default locale. First paint is always the default English shell; personalization swaps happen in the next frame, invisible to the user.