← All articles

How I Migrated Angular to Zoneless and What Changed

Migrating small prerendered Angular sites to zoneless: bundle size gains are modest, but targeted change detection, lower startup overhead, and clearer debugging pay off.

Published
How I Migrated Angular to Zoneless and What Changed cover image
  • Bundle size reduction from going zoneless is limited
  • The real benefits are:
    • Better performance through minimal necessary change detection
    • Lower startup overhead (cost of monkey patching)
    • Better debugging experience / better ecosystem compatibility
  • Aim for zoneless

I moved small websites (Angular prerender sites) to zoneless, so here is a short summary.

What is zoneless?

By default, Angular uses zone.js to trigger change detection. Zone.js hooks JavaScript async work (setTimeout, Promise, XHR, etc.) and, after events finish, tells Angular "something may have changed!" to prompt UI redraw.

Zone.js change detection can hurt performance by running unnecessary checks. Zone.js also adds significant payload size and startup overhead. The Angular team has been working toward Zoneless Angular without Zone.js.

How I went zoneless

Replacing everything with ChangeDetectorRef.markForCheck would be enormous effort—and any miss means change detection never runs—so that was not realistic for me. Instead, I replaced every property that needs change detection (strictly speaking, not identical to template bindings, but readonly properties do not need it) with Signals and switched to Signal-driven detection. See the article below for details.

https://zenn.dev/rdlabo/articles/c6623c6ccc16ddhttps://zenn.dev/rdlabo/articles/c6623c6ccc16dd

After that, I added provideExperimentalZonelessChangeDetection to bootstrap and removed Zone.js, following:

https://angular.jp/guide/experimental/zonelesshttps://angular.jp/guide/experimental/zoneless

Measured results

https://concent-market.com/

Before: 352.51 kB / 102.63 kB

Initial chunk files   | Names                |  Raw size | Estimated transfer size
main-4P6REGVR.js      | main                 | 247.83 kB |                66.46 kB
...
                      | Initial total        | 352.51 kB |               102.63 kB

After: 318.21 kB / 91.27 kB

Initial chunk files  | Names                |  Raw size | Estimated transfer size
main-HCMXWMTQ.js     | main                 | 248.33 kB |                66.61 kB
...
                     | Initial total        | 318.21 kB |                91.27 kB

https://benaton.net/

Before: 1.47 MB / 352.99 kB

Initial chunk files   | Names                |  Raw size | Estimated transfer size
main-FDNOEG6I.js      | main                 |   1.35 MB |               315.58 kB
...
                      | Initial total        |   1.47 MB |               352.99 kB

After: 1.43 MB / 341.48 kB

Initial chunk files  | Names                |  Raw size | Estimated transfer size
main-HRTUBHRQ.js     | main                 |   1.35 MB |               315.60 kB
...
                     | Initial total        |   1.43 MB |               341.48 kB

Analysis

Summing only the browser initial bundle:

concent-market.com Raw size Estimated transfer size
Before 352.51 kB 102.63 kB
After 318.21 kB 91.27 kB
benaton.net Raw size Estimated transfer size
Before 1.47 MB 352.99 kB
After 1.43 MB 341.48 kB

benaton.net was larger to begin with because it uses Firebase Store internally and ships that SDK.

Zone.js is a single file, so as the docs say, zoneless is not mainly about bundle size. Focus on these two points instead:

1. Better performance through minimal necessary change detection

Zone.js ran change detection on the assumption that "something probably changed," which caused unnecessary re-renders and could slow the app.
Going zoneless requires taking manual control of change detection that Zone.js handled automatically (※ I depended on Signal evaluation through Signal migration). That process removes unnecessary re-renders and improves overall performance.

The effect is clearer on larger apps. I have not reached zoneless everywhere yet (※ some libraries I use still depend on Zone.js), but during migration I clearly felt performance improve.

2. Lower startup overhead (cost of monkey patching)

Zone.js monkey-patches standard async APIs such as setTimeout, Promise, and addEventListener so Angular change detection runs after async work. Patch work at startup and change detection after every async call added overhead.
Zoneless removes that overhead.

If you can manualize all change detection, there is little reason to keep the overhead—I recommend moving to zoneless quickly.

3. Better debugging experience / better ecosystem compatibility

I have not felt this personally yet, but Angular writes:

  • ZoneJS makes debugging harder. Stack traces are harder to read in ZoneJS. It is also harder to understand when code breaks outside the Angular zone.
  • ZoneJS works by patching browser APIs, but not every new browser API is patched automatically.

Summary

The interesting part of going zoneless is not "remove ZoneJS and you are done." Removing ZoneJS helps you understand Angular change detection more deeply, and each step can improve performance. If new work and bug fixes leave no room for performance, it may be worth reprioritizing.

See you next time.