Lazy‑Loading Components in StencilJS to Shrink the Initial Bundle
Learn how StencilJS’s @Lazy decorator splits a component into its own bundle, loads it on demand, and what trade‑offs to consider before applying it.
05 Aug 2026, 13:18 UTC

Problem: a large initial JavaScript payload
When a StencilJS application ships many components—especially UI pieces that are hidden until the user interacts with the page—all of their code ends up in the main bundle. This inflates the download size, delays Time‑to‑Interactive, and can hurt Core Web Vitals on slower connections.
Thesis: lazy‑loading a component moves its code out of the initial bundle and loads it only when needed
StencilJS provides a built‑in lazy‑loading mechanism. By marking a component with the @Lazy decorator (or setting lazy: true in @Component metadata), the compiler creates a separate chunk for that component. At runtime a lightweight placeholder is rendered; when the element enters the viewport or receives an interaction, Stencil fetches the chunk via import() and hydrates the component.
How lazy‑loading works in StencilJS
During the build step Stencil’s Rollup‑based bundler analyzes the component graph. Any component flagged as lazy becomes an independent entry point. The emitted HTML contains a comment or empty tag where the component should appear, and a small loader script that triggers the dynamic import when the element becomes visible (using the IntersectionObserver API) or when a programmatic forceUpdate call is made.
Key points
- The lazy component’s JavaScript, CSS, and assets are all placed in its own chunk.
- Stencil’s
hydrateClientfunction is used to instantiate the component after the chunk loads. - Scoped CSS remains intact because the chunk includes the component’s stylesheet.
Worked example: adding a lazy‑loaded tooltip component
Suppose you have a tooltip that appears only when a user hovers over an icon. Making it lazy reduces the initial payload because the tooltip code isn’t needed for the static view.
1. Create the component
import { Component, h, Lazy } from '@stencil/core';
@Component({
tag: 'app-tooltip',
lazy: true, // enables lazy‑loading
shadow: true
})
export class Tooltip {
@State() visible = false;
render() {
return this.visible ? (
) : null;
}
}
2. Build the project
Run the standard Stencil build command:
npm run build
After the build finishes, inspect the www/build directory. You should see a file similar to app-tooltip.js alongside the main bundle (main.js). This separate file is the lazy chunk.
3. Use the component in a page
<app-tooltip>
This text appears on hover.
</app-tooltip>
When the page loads, the network tab shows only the main bundle. Hovering over the host element (or scrolling it into view) triggers a request for app-tooltip.js, after which the tooltip markup appears in the DOM.
Trade‑offs and limitations
Lazy‑loading is beneficial when the component is off‑screen, infrequently used, or behind a user interaction. However, it introduces considerations:
- Extra network request: If the component is needed immediately (e.g., above‑the‑fold header), the additional round‑trip can increase perceived latency.
- SSR hydration: In server‑rendered scenarios the lazy component must be defined on the client before
hydrateClientruns. A version mismatch between server and client bundles can cause hydration mismatches. - Loading state: While the chunk is fetching, the placeholder remains empty. You may want to add a fallback UI or a skeleton loader.
Actionable checklist
- Identify components that are not required for the initial render (modals, tooltips, tabs, etc.).
- Add
lazy: trueor the@Lazydecorator to those components. - Run
npm run buildand verify a separate chunk file appears inwww/build. - Serve the build with a static server, open Chrome DevTools → Network, enable throttling, and confirm the chunk loads only after the component becomes visible or interactive.
- If using SSR, ensure the client and server builds are generated from the same source and that
hydrateClientis called after the lazy chunk has loaded.
By applying lazy‑loading judiciously, you can trim the initial JavaScript payload, improve Time‑to‑Interactive, and keep the user experience smooth without sacrificing the encapsulation and scoping benefits that StencilJS provides.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.