Choosing the Right Lazy‑Loading Strategy for StencilJS Component Libraries
Decide whether to use eager, lazy, or hybrid loading for StencilJS components. Compare constraints, trade‑offs, and see a concrete example that validates on‑demand loading while keeping SSR and legacy browsers happy.
18 Sept 2026, 01:00 UTC

Problem & Decision
When building a reusable component library with StencilJS, the initial bundle size can grow quickly as the number of components rises. A larger bundle delays the first paint, hurts SEO, and may break on browsers that lack native support for import(). The key decision is how to expose components to consumers: eagerly bundle everything, lazily load on demand, or mix the two.
Constraints
- Legacy Browser Support: Some target browsers (e.g., IE11, older Edge) do not support dynamic
import(). Polyfills or a fallback strategy are required. - Server‑Side Rendering (SSR): SSR engines need the component’s JavaScript to be available during pre‑rendering. Lazy‑loaded components may not be rendered unless explicitly pre‑loaded.
- API Predictability: Consumers expect a stable import path and API. Changing the loading mechanism should not alter the public interface.
- Bundle Size vs. Latency: Reducing the initial payload can improve first‑paint times but introduces a small delay when a lazy component is first needed.
Options
Below is a compact table summarizing the three loading strategies:
| Strategy | How It Works | Pros | Cons |
|---|---|---|---|
| Eager Loading | All components are bundled into a single JavaScript file and loaded at page start. | Simple integration, no runtime decisions, works everywhere. | Large initial payload, slower first paint, harder to tree‑shake. |
| Lazy Loading | Components are split into separate chunks and loaded via import() when first used. | Smaller initial bundle, faster first paint, modern browsers get a performance boost. | Requires dynamic import polyfill for legacy browsers; SSR must pre‑render or manually load. |
| Hybrid Loading | Critical components are bundled eagerly; non‑critical ones are lazy. | Balances bundle size and latency; critical path stays fast. | More configuration; need to decide what is “critical”. |
Trade‑Offs Explained
Bundle Size vs. First‑Paint
Eager loading guarantees the component code is available immediately, but the browser must download a larger file before rendering anything. Lazy loading reduces the payload that the browser downloads on page load, improving the time to first paint (TTFP). The trade‑off is a short delay when a lazy component first renders.
Legacy Browser Compatibility
Dynamic import() is a Stage‑4 ECMAScript feature and is not available in IE11 or older Edge. Using lazy loading in such environments requires a polyfill like import‑meta-polyfill or a bundler plugin that rewrites dynamic imports. Eager loading avoids this issue entirely.
SSR & SEO
Search engines crawl the rendered HTML. If a component is lazy‑loaded and not rendered during the SSR pass, its content will be missing from the crawl. To mitigate, either pre‑render critical components or use stencil-build’s ssr option to include the lazy chunks in the SSR bundle.
Developer Experience
Lazy loading introduces a small runtime cost to resolve the component’s module. For libraries that are used in many small pages, this overhead is negligible. For high‑traffic, single‑page applications, the benefit is more pronounced.
Concrete Implementation: Lazy Loading with SSR Support
Below is a step‑by‑step example that demonstrates how to enable lazy loading for a Stencil component named my-card while ensuring SSR compatibility and legacy browser support.
1. Configure Stencil Build for Code‑Splitting
In stencil.config.ts, enable the buildEs5 option for legacy browsers and set outputTargets to include an SSR bundle.
import { Config } from '@stencil/core';
export const config: Config = {
namespace: 'my-lib',
outputTargets: [
{
type: 'dist',
esmLoaderPath: '../loader',
},
{
type: 'dist-custom-elements-bundle',
},
{
type: 'docs-readme',
},
{
type: 'ssr',
dir: 'dist/ssr',
},
],
buildEs5: true, // generates ES5 bundles for legacy browsers
};
2. Mark the Component for Lazy Loading
In the component’s source file (src/components/my-card/my-card.tsx), use the lazy property on the defineCustomElement export. This tells Stencil to treat the component as a separate chunk.
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'my-card',
styleUrl: 'my-card.css',
shadow: true,
// Lazy load: Stencil will create a separate chunk
async: true,
})
export class MyCard {
@Prop() title: string;
render() {
return (
<div>
<h2>{this.title}</h2>
<slot></slot>
</div>
);
}
}
3. Import the Component in the Application
When consuming the library, use Stencil’s async import syntax. The following snippet shows how to load my-card on demand in a plain HTML page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lazy Card Demo</title>
<script type="module">
// Polyfill for legacy browsers
import 'import-meta-polyfill';
// Load the custom elements loader
import { defineCustomElements } from 'my-lib/loader';
// Register the loader so Stencil can resolve lazy chunks
defineCustomElements(window);
// Lazy‑load the component when the button is clicked
document.getElementById('load-card').addEventListener('click', async () => {
const module = await import('my-lib/components/my-card/my-card.js');
// The component is now registered; we can instantiate it
const card = document.createElement('my-card');
card.title = 'Lazy Loaded';
document.getElementById('card-container').appendChild(card);
});
</script>
</head>
<body>
<button id="load-card">Load Card</button>
<div id="card-container"></div>
</body>
</html>
4. Verify the Build Output
- Run the build:
Check thatnpm run builddist/ssrcontains a separate JavaScript file formy-card(e.g.,my-card.js). This indicates successful code‑splitting. - Open the demo page in Chrome DevTools. In the Network panel, click the button. You should see a request for
my-card.jsonly after the click, confirming on‑demand loading. - Test SSR by running a simple Node server that uses
@stencil/core/serverto render the page. Verify that the rendered HTML includes themy-cardmarkup and that the client receives the lazy chunk on demand.
5. Rollback Considerations
Lazy loading changes the runtime behavior of the library. If you later decide to revert to eager loading, simply remove the async: true flag and rebuild. The output will merge all components into a single bundle. No state changes on the consumer side are required.
Practical Checklist
- Does your target audience use browsers that support
import()? If not, include a polyfill. - Are you rendering content on the server? If yes, pre‑render critical lazy components or enable
ssroutput. - Do you have a clear definition of “critical” components for a hybrid strategy?
- Can you afford the small render delay introduced by lazy loading?
- Do you have a build pipeline that can handle ES5 bundles for legacy support?
Conclusion
Lazy loading in StencilJS offers a tangible performance boost for component libraries, especially when the component set is large. By carefully weighing the constraints—legacy browsers, SSR, and API stability—you can choose the right strategy. The example above demonstrates a robust lazy‑loading setup that keeps legacy support and SSR compatibility intact while delivering a lighter initial bundle. Use the checklist to validate your decision before shipping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.