← All articles

Zone-based vs Zoneless Performance in an Ionic Angular App: Measurements and Analysis

Lighthouse comparisons of a mid-size Ionic Angular app show large gains after zoneless migration—especially Total Blocking Time—even though Ionic does not officially support zoneless yet.

Published
Zone-based vs Zoneless Performance in an Ionic Angular App: Measurements and Analysis cover image

I introduced zoneless migration for a small website in the article below.

https://zenn.dev/rdlabo/articles/eab1f30b780ce0https://zenn.dev/rdlabo/articles/eab1f30b780ce0

On a single-page marketing site, though, zoneless gains and overhead reduction are limited—I could not feel a difference. So I tried zoneless on a mid-size Ionic Angular app.

Zone-based vs zoneless performance

Prerequisites

Ionic Angular is a Zone.js-dependent library

The npm package @ionic/angular lists zone.js in peerDependencies.

https://github.com/ionic-team/ionic-framework/blob/main/packages/angular/package.json#L60C6-L60C13https://github.com/ionic-team/ionic-framework/blob/main/packages/angular/package.json#L60C6-L60C13

So even if you go zoneless and remove Zone.js from the project, Zone.js still remains in node_modules through dependencies. Zoneless therefore has no effect on bundle size reduction.

However, as far as I checked internally, Zone.js is used only through the runOutsideAngular method.

https://github.com/search?q=repo%3Aionic-team%2Fionic-framework+this.z.&type=codehttps://github.com/search?q=repo%3Aionic-team%2Fionic-framework+this.z.&type=code

Ionic Angular does not support zoneless yet

Angular has supported zoneless experimentally since v16 (provideExperimentalZonelessChangeDetection), but Ionic Angular is designed around Zone.js, so there is no official support yet.

Also, https://ionicframework.com/docs/angular/lifecycle says this about OnPush:

Components that use ion-nav or ion-router-outlet should not use the OnPush change detection strategy. Doing so will prevent lifecycle hooks such as ngOnInit from firing. Additionally, asynchronous state changes may not render properly.

So not only zoneless—the docs even warn against OnPush for some components. And yet, because Ionic's Zone.js dependency is thin, I tried zoneless anyway and confirmed that both ion-router-outlet and modals using ion-nav work normally.

Note: I asked some users to test an experimental build, and so far I have found no bugs.

I based this on an app many users already run, so I believe common patterns are covered, but Ionic has not announced zoneless support. Following this article does not guarantee zoneless will work for you. Future Ionic or Angular upgrades may change behavior, so test thoroughly.

Hosted in the same environment

For a production Angular build and conditions close to real use, I hosted comparison builds as follows:

Both are on Firebase Hosting. Zoneless differs from zone-based only as follows:

  • Components using ion-nav and ion-router-outlet also use OnPush
  • Import provideExperimentalZonelessChangeDetection
  • Remove Zone.js from the product bundle

There are no other differences, so the comparison is clean. Both backends (and databases) connect to production.

Measurement results

This is not continuous monitoring—I compared locally with Lighthouse (throttled simulation). I ran it 10 times and confirmed the same trend each time, then alternated three more runs each for this article. The tables below are from those last three runs. The login page has few components and almost no difference, so I measured the item list page shown after login. It uses Virtual Scroll with multiple child components, where I expected zone-based vs zoneless to diverge.

Honestly, I expected noise-level differences, so I was surprised how clear the gap was.

Zone-based: https://zone-based.winecode.app/

zone-based.png

Run 1 Run 2 Run 3
First Contentful Paint 7.6 sec 8.6 sec 8.6 sec
Largest Contentful Paint 12.9 sec 12.8 sec 12.8 sec
Total Blocking Time 980 ms 1,110 ms 850 ms
Cumulative Layout Shift 0.057 0.001 0.057
Speed Index 8.1 sec 8.6 sec 8.6 sec

Zoneless: https://zoneless.winecode.app/

zoneless.png

Run 1 Run 2 Run 3
First Contentful Paint 6.4 sec 4.9 sec 6.5 sec
Largest Contentful Paint 7.9 sec 7.5 sec 8.2 sec
Total Blocking Time 560 ms 420 ms 250 ms
Cumulative Layout Shift 0.057 0.057 0.057
Speed Index 6.4 sec 5.3 sec 6.5 sec

Note: Lighthouse scores depend on network and device conditions, so treat them as reference values only.

Summary

"Wow" alone does not make an article, so I checked what changed by counting ngDoCheck runs in app.component.ts.

ngDoCheck runs
Zone-based 112
Zoneless 4

112?! Right. By the way, app-component.ts includes ion-router-outlet, so it is not OnPush. Switching it to OnPush reduced ngDoCheck to 5 even in zone-based mode. That made me wonder what was re-rendering 112 times. Components under ion-router-outlet are created dynamically with createComponent. Each creation changes the view tree, so when the parent runs ngAfterViewChecked, it fires. Adding Ionic components increased ChangeDetectorRef runs, so this explanation holds.

Every Ionic component is wrapped as an Angular component and uses OnPush (※ they detach from ChangeDetectorRef when generated: https://github.com/ionic-team/ionic-framework/blob/main/packages/angular/src/directives/proxies.ts )

component-tree.png

Zoneless suppresses those re-renders, which improves display speed and metrics such as First Contentful Paint.

Zone-based speed and usability were already fine in production, but making it more comfortable is great. Ionic Angular is not officially zoneless yet, but if validation continues without major issues, I hope to escalate to the Ionic team and push toward official support.

See you next time.