Lazy-Load StencilJS Routes Without Extra Configuration
StencilJS emits separate JavaScript chunks per route so visitors only download the code for the page they open. Here is how to verify and tune it.
09 Oct 2025, 12:37 UTC

The problem: one bundle for every page
As a StencilJS site grows, the browser often downloads a single JavaScript bundle that contains every page, even if a visitor only opens the homepage. On a content-heavy blog, that means someone reading an article still pays the download cost for the contact form, archive listing, and admin dashboard code. The result is a slower first paint on mobile and slower time-to-interactive on slow connections.
The useful takeaway: StencilJS can split your application into one JavaScript chunk per route. The initial HTML loads only the code needed for the current URL, and the browser fetches additional route chunks only when the user navigates to them.
What route-level splitting means in Stencil
A route is a URL path such as / or /about. A chunk is a separate JavaScript file produced by the build. Lazy-loading means the browser does not request a chunk until it is needed.
In current Stencil releases, the compiler automatically creates a separate chunk for each route component when you use the Stencil router. The server-rendered or statically generated HTML still loads first, then hydration attaches event listeners. When the user clicks a link, the router updates the URL through the browser History API and requests the matching route chunk.
Older Stencil projects sometimes required explicit router configuration to turn this behavior on. If you inherited an older codebase, check whether @stencil/router is installed and configured before assuming splitting is active.
Worked example: verify splitting after a build
Assume a blog project with two routes: home and about. The root component registers them with @stencil/router.
import { Component, h } from '@stencil/core';
import { Route, Router } from '@stencil/router';
@Component({ tag: 'app-root', shadow: false })
export class AppRoot {
render() {
return (
<Router>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<Route url="/" component="blog-home" exact={true} />
<Route url="/about" component="blog-about" />
</main>
</Router>
);
}
}
Run the production build from the project root. No special permissions are required.
cd my-blog
npx stencil build --prod
Expected check: list the build output and look for route-specific files.
ls dist/build
You should see separate files associated with each route, such as a chunk for blog-home and another for blog-about. Exact names may include hashes, so do not expect a specific filename.
To confirm runtime behavior, open the site in a production-like server:
npx http-server dist
Run this in the project root. It exposes the build directory on a local port, so do not use it on a machine exposed to untrusted traffic.
Open browser DevTools, choose the Network tab, enable "Disable cache," and reload the homepage. The home chunk loads with the page. Click the About link and watch for one additional JavaScript request matching the about route. That request is the lazy-loaded chunk arriving only after navigation.
Trade-off: the first navigation waits for the network
Splitting reduces the initial bundle, but it adds a network hop the first time a user visits a route. If the about chunk is large or the connection is slow, time-to-interactive for that page can feel worse than if the code had already been downloaded.
| Behavior | Without splitting | With route splitting |
|---|---|---|
| Initial download | One bundle containing all routes | Only the current route’s chunk |
| First visit to a new route | No new JavaScript request | Additional chunk request |
| Repeat visits | Browser uses cached bundle | Browser uses cached route chunks |
Service workers can change this picture. If your output target precaches every chunk, the browser downloads more up front but subsequent navigations are instant. For testing, temporarily disable the service worker or bypass the cache so you can see the real per-route requests.
Practical closing
Route-level code splitting is worth enabling when your StencilJS site has more than a few pages and not every visitor sees every page. Verify it by building, listing dist/build, and watching the Network tab while navigating. If you see a separate request appear only when you click a new link, splitting is working.
If the first navigation feels slow, measure the chunk size and consider compressing shared dependencies or prerendering the most visited routes so their HTML arrives without waiting for JavaScript.
Rollback is simple: the build only writes to dist. Delete the output directory, revert any config changes, and rebuild; your source components are untouched.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.