Zone.js is a library that patches browser async APIs and has powered Angular’s change detection mechanism for a very long time.
Starting from version 18, the Angular team started the transition to Angular Zoneless, allowing developers to slowly opt in and migrate their applications. With Angular 20.2, Zoneless became officially stable.
Starting from Angular 21, Zoneless is the new default in Angular. This means that all new applications will not use Zone.js by default.

Why you should migrate to Zoneless
If your Angular app was created before Angular 21, chances are that you are still using Zone.js.
Migrating your app to run it in Zoneless mode and removing Zone.js completely will bring considerable benefits, such as smaller bundle sizes, better performance, and a simpler debugging experience. Plus, it will align with the new Angular standard.
After all, you don’t want to stay behind, do you?
Good news: a gradual transition is possible
I’ve successfully migrated several Angular apps of different sizes and types to Zoneless. From open-source desktop (Electron-based) apps like Keira3 to big enterprise web applications from business clients of mine such as Royal BAM Group and Blinqx.
What I learned is that there is no universal path to migrate to Zoneless, and it all depends on the size and current quality of the codebase.
And my favourite: the transition can be done in steps. After all, I love working with small Pull Requests!
Different codebases, different migration strategies.
I will share here a collection of simple but effective tips that you can use during your migration.
One thing to stress, though: they are tips, and not steps. You don’t have to apply them all, or in any particular order. More about this later in this article.
Update your dependencies first
Using a recent version of Angular and other relevant dependencies (ideally the latest stable) is a recommended initial step.
Third-party libraries are also important here, not just Angular itself. Some older versions of libraries depend on Zone.js and they could be a blocker for you. Libraries such as Angular Material and the Angular CDK already have full Zoneless support.
The closer you are to the latest Angular version, the smoother the migration will be, since Zoneless support has been progressively improved across versions.

Use OnPush Change Detection
Setting the OnPush change detection strategy has been considered a best practice in Angular for a long time, as it gives considerable performance benefits.
If you did your homework and used OnPush, good news: you are already on the right track.
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MyComponent {}
Converting your components to OnPush is not required to migrate to Zoneless, but it is a great step forward, as components using OnPush are much more likely to already be Zoneless compatible.
On top of that, the Angular team has already published an RFC to make OnPush the new default change detection strategy, so migrating your components now will also future-proof your codebase.
Enable Zoneless mode in local development only
You might have to deal with large and messy codebases, where some components might no longer be in use or already planned to be removed later.
In such cases, it is not worth wasting time on migrating all those old components to OnPush, but you may want to use a more lazy approach.
You can enable Zoneless mode in your app, but only in development mode. So production is not affected, and during local development you can test and spot the parts of your applications that need more attention.
You can do that by setting the following in your app root’s configuration:
import { isDevMode } from '@angular/core';
// or @NgModule({ ... }) if you're still using modules
bootstrapApplication(AppComponent, {
providers: [
...(isDevMode() ? [provideZonelessChangeDetection()] : []),
// ...
],
});
If, while using the application for local development, you notice that something does not work properly, or you get some strange console error, it probably means that some component is not working correctly in Zoneless mode.
This is useful once you reach a point where you’re confident that at least the majority of your components work fine, and you want to first have developers use the app in Zoneless mode for a period before shipping it to production.
Of course, this is a transition step. The end goal is still to eventually enable Zoneless in production and remove Zone.js entirely.

Signals to the rescue
Signals are the modern Angular way to manage reactive state. Mastering them has become crucial for any Angular developer and they will be one of your best allies during the migration to Zoneless.
Using Signals will spare you a lot of headaches and allow you to forget about change detection issues: when a signal’s value changes, Angular knows exactly which components need to update.
Describing Signals in detail goes beyond the scope of this article (and there is already plenty of literature online), but I want to give you a small and effective tip:
Whenever a non-immutable variable is used in the HTML template of your component, make it a signal.
The only exception is when you have primitive constants or immutable objects. Then you could just use them as plain non-signal variables.
Alternative to Signals for legacy or edge cases
Another option is to inject the ChangeDetectorRef, and call markForCheck() whenever you want to notify Angular that change detection should run:
import { ChangeDetectorRef, inject } from '@angular/core';
private readonly cdr = inject(ChangeDetectorRef);
// ... something has changed here, for example an http request resolved
// ... some variable got a new value, but this change is not detected
// manually tell Angular that something has changed:
this.cdr.markForCheck();
This might be useful if you are dealing with edge cases or old components that you plan to entirely remove or rewrite later, and do not want to spend time properly refactoring them using Signals.
I would not use it as standard practice, though. Just stick to Signals when you can.
Catch issues early with provideCheckNoChangesConfig
Another useful tool during development is provideCheckNoChangesConfig. It periodically checks your application for bindings that have changed without Angular being notified, a common issue you might bump into when migrating to Zoneless.
You can add it to your development providers like this:
import { isDevMode, provideCheckNoChangesConfig } from '@angular/core';
bootstrapApplication(AppComponent, {
providers: [
...(isDevMode() ? [
provideZonelessChangeDetection(),
provideCheckNoChangesConfig({ exhaustive: true, interval: 3000 }), // add this
] : []),
// ...
],
});
This will check every 3 seconds and throw an error if it finds any stale bindings, helping you identify components that are not yet Zoneless compatible.
Note that provideCheckNoChangesConfig can be used before enabling provideZonelessChangeDetection as well.
Use Unit Tests to check Zoneless compatibility
If you are a big fan of Component DOM Testing like me, you are in luck: you can already check whether your components would run fine in Zoneless mode by providing this to your test setup:
TestBed.configureTestingModule({
providers: [
provideZonelessChangeDetection(),
// ...
]
});
I’ve already dedicated a separate article to this topic:
Migration strategy tips
As mentioned, the steps to migrate to Zoneless depend on your source code size and “health” status.
For example, if your application is small, or if it is in good shape and most of the components already use OnPush, then it might be worth first migrating all components to OnPush and then directly switching to Zoneless mode. Give it a try and eventually remove Zone.js completely as a dependency.
If your source code is large and messy, then the migration would require more time. For example, first require all new components to be OnPush and have their unit tests enable Zoneless mode. So you make sure all the new features are already Zoneless-compatible. In parallel, migrate the existing features, starting with those features where there are no plans to make big changes.
I’ve dealt with both migration types, sometimes combining approaches depending on the situation and the time available, and there is no single recipe that works for everyone.
It is important that you clearly communicate to the project management that this is a very good investment that will make your application future-proof, increase performance, and save you and your team a lot of trouble in the future.

Conclusions
- Zoneless is the new default in Angular 21+
- Migrating your app to Zoneless brings many benefits, such as smaller bundle sizes, better performance, and simpler debugging
- The migration can be done gradually in steps
- It is always better to upgrade Angular and other dependencies to a recent version before migrating
- There is no single universal path; the migration steps depend on your source code's current state
- Converting your components to OnPush brings many benefits and makes them very likely already Zoneless compatible
- You can enable Zoneless in development mode as a safe intermediate step
- Use Signals for any variable used in a component template that can change over time
- You can use unit tests with
provideZonelessChangeDetection()to verify Zoneless compatibility in advance
Comments
Loading comments…