Lazy-Loaded Component Bundles Leave Blank Placeholders on Network Failure
0 reputation · 04 Oct 2022, 17:10 UTC
0 reputation · 04 Oct 2022, 17:10 UTC
StencilJS lazy-loads components by injecting a <script> tag for each component bundle. When the network request for that bundle fails, the component never renders and the page shows an empty placeholder with no error surfaced inside the component's shadow DOM.
window.onerror or the script element's onerror, which are not scoped to a specific component.component-name.js) are not guaranteed stable across builds, making programmatic retry difficult.<script type="module"> contexts; legacy IE11 builds silently ignore it.Determine whether a declarative fallback mechanism or configuration option can be added to Stencil's lazy-loading pipeline so that failed component loads can be recovered safely without custom workarounds.
What is the recommended pattern for detecting a lazy-load failure at the component level?
Can a fallback component be specified declaratively in the component's TSX or via Stencil config?
Is there a stable identifier for a failed bundle that would allow a retry strategy?
33775 reputation · 05 Oct 2022, 00:28 UTC
No. As of current Stencil releases there is no declarative fallback mechanism — no config option, no TSX property, and no component-scoped load-failure event in the lazy-loading pipeline. When the per-component <script> injected via dynamic import() fails, the custom element stays unregistered, so it renders as an empty unknown element. The recommended pattern today is a wrapper-level error boundary driven by a global error listener, plus a build manifest if you need retry. None of this is official API, so treat it as a workaround that could change between major Stencil versions.
The failure happens before your component class exists. The loader injects a script tag; if the request 404s or the network drops, the promise behind the dynamic import rejects and the tag name is simply never upgraded. Your component's lifecycle methods (componentDidLoad, componentDidRender) never run, so nothing inside the component can observe its own failure. The only signals are:
window.onerror / window.addEventListener('error', ...) — fires, but the filename is a hashed bundle URL shared across many components, so attribution is fuzzy.onerror — scoped, but you don't control the element Stencil creates.import() — again global, not per-component.Since the failed element never upgrades, you can exploit that: an unregistered custom element is just an inert unknown tag. A parent (or a small utility) can poll for upgrade and render a fallback when it doesn't happen:
async function withFallback(tagName, host, renderFallback, timeoutMs = 8000) {
try {
await Promise.race([
customElements.whenDefined(tagName),
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), timeoutMs))
]);
} catch {
renderFallback(host); // e.g., insert a static message or retry UI
}
}
Combine this with a global error listener that logs event.filename so you can distinguish a genuine bundle failure from a slow network (timeout false positives are the main risk — tune the timeout against your slowest expected connection).
Not currently. There is no errorComponent, fallback, or lazy-loading failure hook in Stencil's config or component decorators. The closest declarative approach is to always render a lightweight loading skeleton inside the host's slot/default content, and have the wrapper replace it with an error state on timeout. That keeps the fallback markup in your TSX, even though the trigger logic lives outside the component.
No. Output bundle filenames carry build-dependent hashes, and Stencil does not expose a component-to-URL mapping at runtime. A retry strategy therefore needs a manifest you generate yourself at build time (mapping component tags to emitted chunk names) and a runtime that rewrites the failed URL — significant plumbing with no official support. For most apps, refreshing the page after showing a fallback message is the pragmatic recovery path.
customElements.whenDefined never resolves while window.onerror fires.One caveat: if your placeholders are blank even on successful loads, that points to a different problem (hydration or slot config), and the timeout-based detection above would need rethinking.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.