Skip to content

When things go wrong

Nothing here throws, and no failure blanks copy a visitor is reading. Copy that already landed stays; anything unfinished falls back to what you wrote.

If our service is down, your copy stays on the page

Section titled “If our service is down, your copy stays on the page”

The request fails and state.phase becomes failed. Fields that finished keep the copy they delivered; a field the stream opened but never finished is repainted with your whole fallback and demoted to source: "static", so its AI label goes with it. A field still blank gets your fallback. state.error carries a readable message you can log. No exception reaches your render tree, no element collapses, nothing shifts.

There is no retry loop and no reconnection storm: one attempt per block per page view.

On a slow network, the visitor reads your copy and may never see a swap

Section titled “On a slow network, the visitor reads your copy and may never see a swap”

On an ordinary client-side mount nothing is on screen yet. The hold arms the moment the block mounts, before the browser ever paints, covering even a wait that precedes the request itself, such as a pinned sibling waiting on the page’s variant draw. The block waits over your artwork rather than flashing copy it is about to replace: 2.5 seconds, restarted once if the stream proves it is alive, so up to 5 seconds. The wait reserves rather than blanks: your fallback is drawn invisibly in its exact space, so nothing collapses while it lasts. A connect that hangs or never answers is the same case: at the end of the budget your fallback types into the space it already holds, and anything that arrives later lands as the one permitted change. Every entrance is a typed entrance, fallback included. On a client-side mount that is the only way a visitor reads your fallback first and then watches it change.

If the block hydrated over server-rendered HTML, your fallback is already being read: it is exempt from the hold, and it is never typed over. Adapted text buffers off-canvas and lands in one piece, at most one composed change for the whole page view.

If the campaign is paused or out of budget, real copy still renders

Section titled “If the campaign is paused or out of budget, real copy still renders”

The server can serve a fixed, pre-decided version instead of generating one. That is not an error: the words are real, they were simply not adapted for this visitor. state.degraded names the reason and state.contentSource says static, so nothing claims adapted provenance. Correctly, because nothing was generated.

Hero puts it on the element as data-degraded, if you want to annotate it.

state.phaseOn screen
idleYour fallback. Nothing requested yet.
holdingYour artwork, replaceable copy briefly withheld, its space reserved by your invisible fallback.
liveAdapted copy, arriving.
settledFinal copy for this page view.
failedDelivered copy stands; anything unfinished or blank is repainted with your fallback.

Point the provider at a host that cannot answer:

<SparkletreeProvider apiBase="https://127.0.0.1:9" organizationId="org_123">

Expect: your fallback, readable; state.rewrites === 0; no adapted provenance; no layout shift. Verify this before you verify the happy path. It is the behaviour that runs during an incident.

For previews, Storybook and unit tests, skip the network instead of faking a broken host:

useIsland({ campaignId, island: "hero", fallback, enabled: false });

The one failure you have to fix yourself: a missing ref

Section titled “The one failure you have to fix yourself: a missing ref”

useIsland returns a ref that must be attached to the element which has to be visible for the view to count. Forget it and the campaign renders perfectly while reporting zero impressions. Because the SDK tells the server it owns the count, nothing else counts it either.

const { state, ref } = useIsland({ … });
return <section ref={ref}>…</section>;

Hero and Cta attach it for you. There is no warning when a custom component does not.

Failures the SDK absorbs without telling you

Section titled “Failures the SDK absorbs without telling you”
  • A blocked or throwing localStorage. The visitor’s “standard version” preference cannot be stored; copy keeps counting as adapted (decideChrome stays honest), which is the safe default. Pass your own storage if a consent manager gates it.
  • A missing IntersectionObserver. The impression fires immediately instead of waiting for visibility. Over-counting is visible in your numbers; under-counting would be a silent bias in figures you are billed on.
  • Missing context signals. A hardened browser or in-app webview may withhold language, device or timezone. The creative adapts less well; nothing breaks.
  • An API version your deployment does not implement. Your first paint stands and no retry happens.
  • A self-hosted apiBase that arrived through a publishableKey. The request that writes new fragment copy is withheld, so fragments serve your fallbacks and never warm up. This is the one entry here that does not resolve itself with time. Pass apiBase and organizationId to the provider explicitly. A dev build warns once in the console; see signed minting.

Painted text stays painted, your artwork stays, your DOM keeps its order, your scripts keep running, and no spinner is left spinning. A sentence is never left half-typed either. An abandoned field is repainted whole from your fallback, so the visitor always reads something finished.