Route-Based Code Splitting Implementation for Web Applications

If an application loads everything at once, users leave before it finishes. We set up route-based build splitting turnkey: initial load is reduced several times over, and each page delivers only the code it needs. We'll discuss your task and come back with a plan.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113
  • Website development for SBH Partners
    Website development for SBH Partners
    1201
  • Website development for Red Pear
    Website development for Red Pear
    619

Code splitting by routes: where loading speed breaks

A single bundle stops being harmless exactly at the moment when an application outgrows a dozen routes.

The bundler packs the landing page, the admin panel, and the reports section into one file — the browser is forced to download and parse all of it before the first render, even if the user only opened the login form.

Bundle size grows non-linearly: each new heavy dependency drags along transitive modules, and the dependency tree between routes almost always overlaps.

Then the duplication effect kicks in. If sections are built independently and without a shared configuration, the same module ends up in several chunks — the browser downloads it again, the cache doesn't kick in, and instead of saving traffic you get a regression.

Time to interactive on heavy routes climbs: while unnecessary code is being parsed, buttons don't respond, and the user thinks the interface has frozen.

Lazy loading of modules by routes solves this at the architecture level, not cosmetically. Split points are placed at route boundaries, shared dependencies are moved into a separate chunk, and prefetching is wired up for those transitions the user is highly likely to make. Then the landing page serves only its own code, and the rest is pulled in as navigation happens.

Let's break down how this is addressed: where exactly to place split boundaries, how not to create duplicates, and what to measure the result with before and after.

What route-based code splitting delivers

Route-based code splitting is when each page of an application loads only its own code rather than the entire project as a whole.

Instead of a single initial bundle that drags everything onto the first screen at once, the bundler slices the code into chunks and loads them as the user navigates.

As a result, the initial bundle shrinks severalfold: only what is needed to render the first screen arrives upfront, while heavy sections — analytics, editors, maps — are pulled in later, once the user reaches them.

Page load time drops not linearly but in a leap: the browser downloads a smaller volume faster, starts parsing and rendering the markup sooner, and the first paint occurs noticeably earlier.

This is especially visible on mobile networks and slow connections, where every extra kilobyte of the initial bundle directly pushes back the moment the user sees content.

Chunk caching works in favor of speed as well: route code changes less often than the overall application shell, so when one section is updated, the browser reuses the already-loaded chunks of the others.

The user gets a fresh version of the page without re-downloading everything, and repeat visits happen almost instantly.

Failure isolation is a separate benefit that becomes apparent in production. If one route crashes due to an error in its own code, the others keep working: the failure is localized within the chunk rather than bringing down the entire application.

This simplifies both root-cause analysis and rollback — it is enough to restore the previous version of the specific section.

We verify the effect through metrics: initial bundle size before and after, time to first paint, the share of routes extracted into separate chunks, and the number of repeat downloads when a release is updated.

If the initial bundle has not shrunk or the chunks duplicate shared code, then the splitting was done only formally, and we see this in the numbers, not in words.

Approaches to Code Splitting: Comparing Strategies

Code splitting is a trade-off between first render speed and the number of network requests. The finer the chunks, the faster the page starts, but the greater the overhead of loading them and the risk of a "waterfall" during navigation. Below are three working schemes that are realistically chosen between in practice.

Option When it fits Limitations
Route-based splitting A large application with clear sections: catalog, account, admin panel. Each route is a separate entry point via dynamic import Shared code is duplicated across chunks unless it is extracted into a common module; navigating between sections causes a delay without prefetching
Component-based splitting Heavy widgets within a single page: editor, charts, maps, modal windows Many small requests; without proper chunk prefetching, the user sees "flickering" on first interaction
Hybrid scheme Production applications where both startup and navigation matter: routes as the foundation, heavy components as separate chunks More complex build configuration and prefetch rules; control is needed to keep chunks from growing

How It Is Chosen in Practice

For most projects, route-based splitting becomes the foundation: it provides a predictable structure and a clear entry point for each section.

On top of it, heavy components are selectively extracted — this way the first render stays fast, and rare scenarios don't drag the entire codebase along with them.

Chunk prefetching addresses the main drawback of the scheme — the delay during navigation. Links to likely next routes are warmed up in advance, and the user doesn't wait for loading at the moment of the click.

The hybrid scheme requires discipline: splitting rules are fixed in the build configuration, and chunk size is checked on every release, otherwise splitting turns into a source of regressions.

Stages of implementing build splitting

Implementing build splitting is not "flipping a checkbox in the config," but a sequence of engineering steps with a verifiable result at each one. Below is the process we follow: from auditing the current bundle to handover with before/after metrics.

  1. Build audit. We break down the current bundle by chunks: we look at what ends up in the initial loader, which dependencies drag heavy libraries along with them, and where modules are duplicated. The output is a map of "what always loads versus what is only needed on a specific screen" and a list of obvious candidates for extraction.
  2. Route boundary scheme. We design exactly where to cut the application: by top-level routes, by nested sections, or by scenarios within a single screen. We lock shared modules (authentication, header, design system) into a separate common chunk so they are not duplicated in every piece.
  3. Bundler configuration. We translate the scheme into configuration: dynamic imports at route boundaries, chunk naming, extraction of vendor dependencies, prefetching of likely next screens. We separately configure behavior when a chunk fails to load — the user must not see a white screen.
  4. Metrics verification. We compare before/after: initial bundle size, time to first render, number of requests on the first screen, share of code loaded on demand. We look not at averages, but at the worst case — a slow connection and a cold cache.
  5. Regression and edge cases. We run through transitions between routes, direct visits via deep links, back navigation through history, and rollback to the previous version of the application when chunks are left unclosed.
  6. Documentation and handover. We hand over the boundary scheme, the bundler configuration, and the rules for adding new routes, so that the next developer does not break the splitting with a single import line.

Each stage ends with an artifact that can be verified, rather than a verbal "done."

Configurations, route maps, and output regulations

Route splitting lives not in code, but in configuration and agreements.

If chunk boundaries are defined only "by eye" in a single bundler file, after a couple of releases no one will remember why the payment route loads together with the catalog, and a new developer will add a page to the shared bundle. That's why we capture the result in artifacts that can be read without us.

  • Build configuration — splitting rules: which routes get a separate chunk, what goes into the shared bundle (shared components, API client, styles), how files are named. With comments explaining why the boundary is drawn exactly here.
  • Route map — a table of "route → chunk → what's in it → when it loads." It shows where dependencies intersect and which route drags along extra weight.
  • Caching rules — headers for hashed chunks and for entry points: immutable files are cached long-term, the entry point is revalidated. Separately — deployment behavior, so the user doesn't get an old entry point with new chunks.
  • Preloading and priorities — which routes are preloaded on navigation, which on hover, which only on click. This is a deliberate decision, not a side effect.
  • Documentation — how to read the route map, where to check chunk sizes, what to do when the bundle grows.
  • Route addition regulations — a step-by-step checklist: create a route, assign a chunk, check intersections, update the map, run the size check in the build.
  • Build checks — automated control: if a chunk exceeds the threshold or a route ends up in the shared bundle, the build warns. The rule works without reminders.

Such a set survives a contractor change: chunk boundaries, caching, and the order of adding pages are described explicitly, rather than reconstructed from commit history.

Case Study: Reducing Initial Load Time of a Personal Dashboard

A banking client's personal dashboard: a single initial bundle of 2.8 MB, time to interactive on an average mobile connection — 6.4 s.

On slow connections (3G, congested Wi-Fi in shopping malls), the bounce rate before first interaction reached 23% — the user saw a white screen and left before the app had time to render.

We broke down the routes by frequency of use: login, balance, transaction history, transfers, settings. Each route got its own entry point, shared code was moved into a separate chunk, and heavy widgets (charts, payee details editor) are loaded on demand via dynamic import.

Preloading of adjacent routes is enabled only after the first interaction — on a slow connection, this doesn't steal bandwidth from critical code.

Metrics before and after

Metric Before After
Initial bundle 2.8 MB 640 KB
Time to interactive (average mobile) 6.4 s 2.1 s
Bounce rate before first interaction 23% 7%

The initial bundle shrank by 4.4x — the login screen now loads only the authorization form and the shared runtime. Time to interactive dropped threefold, and the bounce rate on slow connections went from 23% to 7%.

Conversion to first login increased by 11 percentage points with no changes to the authorization flow itself.

We also set up real-time performance metrics: breakdown by route, load time of each chunk, and the share of users for whom interactivity doesn't occur within 4 s. This makes it possible to catch regressions with every release, rather than a month later based on complaints. The tool is webpack-bundle-analyzer for monitoring bundle composition at the build stage.

How does route-based splitting differ from component-based splitting?

Route-based splitting is a strategy in which the application entry point and each top-level screen are assembled into a separate chunk, and navigation between sections loads the required piece on demand.

Component-based splitting works one level lower: individual blocks within a single screen are broken up — modal windows, heavy widgets, editors.

The difference is not in the tool, but in the granularity of the chunks and in who controls the moment of loading: with route-based division, navigation does this, while with component-based division, it is the logic of the interface itself.

Granularity directly determines overhead. Each chunk is a separate network request with its own header, handshake, and time to first byte. Five to seven route chunks produce a predictable picture: the user downloads exactly the section they entered.

Splitting by components easily runs into dozens of small files, and on a slow connection the total overhead begins to eat up the gains from the smaller volume — especially when components load in a cascade, one after another.

The browser cache is the second dividing line. Route chunks change rarely and in a targeted way: an edit in one section does not invalidate the others, so on the next deploy the user re-downloads only the affected piece, while everything else is taken from the cache.

With component-based division, file hashes are recalculated more often, and the boundary between "updated" and "remained the same" blurs — the share of re-downloaded content grows.

In practice, the strategies do not compete but nest within each other. A route defines large, stable chunks and a predictable cache, while inside a heavy screen individual components are singled out in a targeted way — those that are not needed immediately or are needed rarely.

This order keeps the number of requests under control and preserves the gains from deferred loading where they are actually noticeable.

Technical concerns before implementation

Splitting the bundle by route changes how the browser receives code: instead of one large file, it loads chunks as the user navigates. This affects indexing, caching, and release rollback — let's go through each point separately.

Will SEO suffer if part of the code is loaded on demand?

Search engine robots execute JavaScript and see the rendered content, so deferred chunk loading by itself does not hide content. The risk lies elsewhere: if a route's entry point is loaded only on click, the robot may never reach it.

That's why we serve critical routes with server-side rendering or prerendering, and keep links in the markup real — then indexing does not depend on whether the navigation fired.

After implementation, we reconcile the sitemap with the actually indexed URLs and the coverage in the webmaster panel.

How does prefetching work, and does it eat into the benefits of splitting?

Prefetching runs on an idle channel and by intent: hovering or touching a link, or its appearance in the viewport. The current route's chunk has priority, the rest is loaded in the background.

If you prefetch everything indiscriminately, the splitting effect is lost — so we limit the warm-up list to 2-3 likely navigations and revisit it based on analytics data.

What about cache invalidation on update?

Chunk names are tied to content, so a new release gets new URLs, while the old ones remain in the browser cache and don't get in the way. We serve the entry point and markup with long caching disabled — otherwise the user gets stuck on the previous version.

During deployment, we make sure that the chunk references and the files themselves land in storage in a single operation.

How do you roll back changes if a release causes a regression?

A rollback means restoring the previous entry point and the previous set of chunks; they are not overwritten. We keep several recent builds, so reverting takes minutes and requires no rebuild.

Before deployment, we run smoke checks across routes to catch chunk loading breaks before they reach users.

If any of these scenarios have already bitten you, describe the task, and we'll come back with an estimate and a plan for your stack.

Let's discuss your build splitting task

To come back with an estimate and an implementation plan rather than generalities, we need baseline data about your application. The more precise the inputs, the fewer assumptions in the estimate and the more realistic the timelines. Below is what we clarify and what we expect from you.

What we will clarify on our side:

  • the current build structure: a single shared bundle or already manually split into several files;
  • the route and entry point map — which screens load first, which load rarely;
  • the size and composition of dependencies: what gets pulled into the initial chunk and whether it can be deferred;
  • whether server-side rendering is present and how it combines with lazy loading;
  • requirements for behavior during navigation: loading indicators, prefetching of likely routes;
  • the current release process and how changes are rolled out without downtime.

What we need from you to estimate the work:

  • access to the repository or an export of the build configuration and route map;
  • a description of target metrics: time to interactive, initial chunk size, share of deferred code;
  • a list of browsers and devices that must be supported;
  • infrastructure constraints: where static assets are hosted, whether a CDN is in place, how caching is configured.

How we will come back with an implementation plan. We will break the work into stages: audit of the current build, designing split boundaries by route, setting up lazy loading and prefetching, verifying behavior during navigation and rollback in case of regression.

For each stage we will attach the expected effect in measurable terms — initial chunk size, time to interactive, share of code loaded on demand.

Describe the task in free form — what the application is, what routes it has, and what concerns you about the current loading. We will come back with a work estimate and a stage-by-stage implementation plan.