<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sakthikumaran Navakumar</title>
    <description>The latest articles on DEV Community by Sakthikumaran Navakumar (@sakthicodes22).</description>
    <link>https://dev.to/sakthicodes22</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3765235%2Fe0f10403-ca82-4885-8a49-56f60d5aac0e.jpeg</url>
      <title>DEV Community: Sakthikumaran Navakumar</title>
      <link>https://dev.to/sakthicodes22</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sakthicodes22"/>
    <language>en</language>
    <item>
      <title>Build-Time, End to End - From federation.config to remoteEntry.json</title>
      <dc:creator>Sakthikumaran Navakumar</dc:creator>
      <pubDate>Tue, 06 Oct 2026 02:00:49 +0000</pubDate>
      <link>https://dev.to/sakthicodes22/build-time-end-to-end-from-federationconfig-to-remoteentryjson-1980</link>
      <guid>https://dev.to/sakthicodes22/build-time-end-to-end-from-federationconfig-to-remoteentryjson-1980</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 3 of an 8-part series on Native Federation v4 for enterprise architecture teams. New to the series? Start with &lt;a href="https://dev.to/sakthicodes22/the-mental-model-revisited-why-native-federation-exists-and-what-changed-in-v4-1klg"&gt;Part 1: The Mental Model, Revisited&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In Part 2, we mapped the four layers of Native Federation v4 and saw that one file, &lt;code&gt;remoteEntry.json&lt;/code&gt;, connects the build to the browser. This article zooms in on the build side and answers three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What do you write in &lt;code&gt;federation.config.mjs&lt;/code&gt;, and what does each setting mean?&lt;/li&gt;
&lt;li&gt;What happens, step by step, when you run the build, and what files come out?&lt;/li&gt;
&lt;li&gt;How does a host find remotes it has never seen when it was built?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything here is based on the official docs and the source code of &lt;a href="https://github.com/native-federation/native-federation-core" rel="noopener noreferrer"&gt;native-federation-core&lt;/a&gt; and the &lt;a href="https://github.com/native-federation/angular-adapter" rel="noopener noreferrer"&gt;Angular adapter&lt;/a&gt;, and links point to both so you can check the details yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few terms first
&lt;/h2&gt;

&lt;p&gt;Part 2 explained host, remote, shared dependency, bundler, import map, semver and singleton. This article adds four more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;External:&lt;/strong&gt; a library that is deliberately left &lt;em&gt;out&lt;/em&gt; of your app's bundle, so it can be loaded separately and shared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exposed module:&lt;/strong&gt; a piece of your app, such as a component or a set of routes, that other apps are allowed to load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entry point:&lt;/strong&gt; the file a build or a code scan starts from. In Angular, that's usually &lt;code&gt;src/main.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manifest:&lt;/strong&gt; a list of remote names and the URLs of their &lt;code&gt;remoteEntry.json&lt;/code&gt; files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Normal build vs. federated build
&lt;/h2&gt;

&lt;p&gt;In a normal Angular build, your code and every library you use are bundled together. If three apps all use Angular, users download Angular three times.&lt;/p&gt;

&lt;p&gt;A federated build splits things up. Your own code is built without the shared libraries inside it. Each shared library gets its own file, so the browser can download it once and reuse it. And the build writes two small description files, &lt;code&gt;remoteEntry.json&lt;/code&gt; and &lt;code&gt;importmap.json&lt;/code&gt;, so the browser knows what's available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6dsdbjgi7u7idpcyvfm9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6dsdbjgi7u7idpcyvfm9.png" alt="A normal build produces one main.js with everything inside. A federated build takes your code and federation.config.mjs and produces main.js with your code only, one file per shared library, exposed modules for remotes, and remoteEntry.json plus importmap.json." width="800" height="790"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The input: federation.config.mjs
&lt;/h2&gt;

&lt;p&gt;Every federated project has a &lt;code&gt;federation.config.mjs&lt;/code&gt; next to it. Here is the one the Angular adapter's &lt;code&gt;ng add&lt;/code&gt; schematic generates for a remote today, taken from its &lt;a href="https://github.com/native-federation/angular-adapter/blob/main/src/schematics/init/files/federation.config.mjs__tmpl__" rel="noopener noreferrer"&gt;template in the source code&lt;/a&gt; (comments shortened):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withNativeFederation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromPackageJson&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-architects/native-federation/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;withNativeFederation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;exposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/app/app.component.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fromPackageJson&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;package&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="c1"&gt;// Share all of @angular/core to prevent version mismatches&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;includeSecondaries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keepAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;

  &lt;span class="na"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs/ajax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs/fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs/webSocket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;denseChunking&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go through it piece by piece.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing what to share
&lt;/h3&gt;

&lt;p&gt;There are three ways to build the &lt;code&gt;shared&lt;/code&gt; list, all defined in &lt;a href="https://github.com/native-federation/native-federation-core/blob/main/src/lib/config/share-utils.ts" rel="noopener noreferrer"&gt;share-utils.ts&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fromPackageJson()&lt;/code&gt;&lt;/strong&gt; is the recommended way. It reads the dependencies in your &lt;code&gt;package.json&lt;/code&gt; and shares all of them with the settings you pass in. Exceptions are handled with &lt;code&gt;.skip()&lt;/code&gt;, &lt;code&gt;.override()&lt;/code&gt; and &lt;code&gt;.patch()&lt;/code&gt;. The example above uses &lt;code&gt;.patch()&lt;/code&gt; to change the settings of just &lt;code&gt;@angular/core&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;shareAll()&lt;/code&gt;&lt;/strong&gt; is the older version of the same idea. It still works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;share()&lt;/code&gt;&lt;/strong&gt; is for picking libraries by hand, one by one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you leave &lt;code&gt;shared&lt;/code&gt; out entirely, Native Federation behaves as if you had written &lt;code&gt;fromPackageJson({ singleton: true, strictVersion: true, requiredVersion: 'auto' })&lt;/code&gt; (&lt;a href="https://native-federation.com/docs/v4/core/configuration/" rel="noopener noreferrer"&gt;Core configuration&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  The settings on each library
&lt;/h3&gt;

&lt;p&gt;Each shared library carries four settings that matter later, when the browser decides which version to load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;singleton&lt;/code&gt;:&lt;/strong&gt; only one copy of this library may exist on the page. Frameworks like Angular need this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;strictVersion&lt;/code&gt;:&lt;/strong&gt; if another app needs a version that isn't compatible, don't just use the shared one anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;requiredVersion&lt;/code&gt;:&lt;/strong&gt; the range of versions this app accepts. &lt;code&gt;'auto'&lt;/code&gt; means "use the range from &lt;code&gt;package.json&lt;/code&gt;," for example &lt;code&gt;^20.1.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;version&lt;/code&gt;:&lt;/strong&gt; the exact version this app was built with. By default it's read from the installed package in &lt;code&gt;node_modules&lt;/code&gt;, not from &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how that plays out. Say your &lt;code&gt;package.json&lt;/code&gt; lists &lt;code&gt;"@angular/core": "^20.1.0"&lt;/code&gt; and &lt;code&gt;npm install&lt;/code&gt; gave you 20.1.4. The build records that this app &lt;em&gt;has&lt;/em&gt; 20.1.4 and &lt;em&gt;accepts&lt;/em&gt; anything from &lt;code&gt;^20.1.0&lt;/code&gt;. The browser later uses both numbers to find one version every app can agree on. Part 5 covers that decision in detail.&lt;/p&gt;

&lt;p&gt;Notice that the defaults are strict. Unless you change them, &lt;strong&gt;every library is shared as a strict singleton&lt;/strong&gt;. That's safe, but it's worth knowing before you see your first version warning.&lt;/p&gt;

&lt;h3&gt;
  
  
  skip: leaving a library out of sharing
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;skip&lt;/code&gt; takes package names, regular expressions or small functions. The important detail: skipping a library &lt;strong&gt;does not remove it&lt;/strong&gt;. It still gets installed and still works, but it's built into your app's own bundle instead of being shared. The template above skips parts of RxJS that most apps never use at runtime.&lt;/p&gt;

&lt;p&gt;Some packages are skipped automatically, through a &lt;a href="https://github.com/native-federation/native-federation-core/blob/main/src/lib/config/default-skip-list.ts" rel="noopener noreferrer"&gt;default skip list&lt;/a&gt;: Native Federation's own packages, &lt;code&gt;es-module-shims&lt;/code&gt; and all &lt;code&gt;@types/*&lt;/code&gt; packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  includeSecondaries and keepAll: the sub-paths
&lt;/h3&gt;

&lt;p&gt;Many libraries have sub-paths, called secondary entry points, like &lt;code&gt;@angular/common/http&lt;/code&gt; or &lt;code&gt;@angular/core/rxjs-interop&lt;/code&gt;. &lt;code&gt;includeSecondaries&lt;/code&gt; controls whether those are shared too.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;includeSecondaries: { keepAll: true }&lt;/code&gt; goes one step further. It tells the build to keep &lt;em&gt;every&lt;/em&gt; sub-path of a library as long as the library itself is used, even sub-paths your app never imports. The next section explains why the template does this for &lt;code&gt;@angular/core&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the build decides what's "used"
&lt;/h2&gt;

&lt;p&gt;Sharing everything in &lt;code&gt;package.json&lt;/code&gt; would be wasteful. Most projects list libraries they barely touch. So Native Federation has a feature called &lt;strong&gt;&lt;code&gt;ignoreUnusedDeps&lt;/code&gt;&lt;/strong&gt;, which is on by default (&lt;a href="https://github.com/native-federation/native-federation-core/blob/main/src/lib/config/with-native-federation.ts" rel="noopener noreferrer"&gt;with-native-federation.ts&lt;/a&gt;). It scans your code, finds which shared libraries are actually imported, and drops the rest from the shared list.&lt;/p&gt;

&lt;p&gt;The interesting question is &lt;em&gt;where the scan starts&lt;/em&gt;. The answer depends on whether the project is a host or a remote, and it's easy to get wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8nv9g5qdcsjwefsi83ey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8nv9g5qdcsjwefsi83ey.png" alt="Flow showing how ignoreUnusedDeps decides what is used. If the project exposes modules, only the exposed files are scanned. If not, the scan starts at main.ts, follows import('./bootstrap'), then the app config, routes and lazy-loaded components. Then libraries imported by those libraries are added, sub-paths are kept or dropped depending on keepAll, and everything else is removed." width="800" height="2022"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the project exposes modules (a remote), the scan starts from the exposed files only.&lt;/strong&gt; It does not look at &lt;code&gt;main.ts&lt;/code&gt; or &lt;code&gt;bootstrap.ts&lt;/code&gt; at all. You can see this in &lt;a href="https://github.com/native-federation/native-federation-core/blob/main/src/lib/config/get-used-dependencies.ts" rel="noopener noreferrer"&gt;get-used-dependencies.ts&lt;/a&gt;: it takes the files listed in &lt;code&gt;exposes&lt;/code&gt;, and only uses the fallback entry points when that list is empty. The Angular builder's &lt;a href="https://github.com/native-federation/angular-adapter/blob/main/src/builders/build/schema.json" rel="noopener noreferrer"&gt;&lt;code&gt;entryPoints&lt;/code&gt; option&lt;/a&gt; confirms this. Its description says exposed modules "always take precedence," so you can't use it to override or narrow them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the project exposes nothing (a typical host), the scan starts at &lt;code&gt;src/main.ts&lt;/code&gt;.&lt;/strong&gt; In a federated Angular app, &lt;code&gt;main.ts&lt;/code&gt; is tiny. It starts federation, then loads the real app with a dynamic import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;initFederation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;federation.manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hostRemoteEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./remoteEntry.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bootstrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner uses TypeScript's import detection, which sees dynamic &lt;code&gt;import()&lt;/code&gt; calls as well as normal imports. So it follows &lt;code&gt;import('./bootstrap')&lt;/code&gt; into &lt;code&gt;bootstrap.ts&lt;/code&gt;, then into your app config, your routes, and the components those routes lazy-load. Anything reachable from there counts as used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then the scan adds what your libraries need.&lt;/strong&gt; If you use &lt;code&gt;@angular/router&lt;/code&gt;, the build also counts the packages &lt;code&gt;@angular/router&lt;/code&gt; itself imports, such as &lt;code&gt;@angular/common&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, sub-paths are checked.&lt;/strong&gt; Without &lt;code&gt;keepAll&lt;/code&gt;, a sub-path like &lt;code&gt;@angular/core/rxjs-interop&lt;/code&gt; is kept only if something imports it exactly. With &lt;code&gt;keepAll&lt;/code&gt;, it's kept whenever its parent package is used (&lt;a href="https://github.com/native-federation/native-federation-core/blob/main/src/lib/config/remove-unused-deps.ts" rel="noopener noreferrer"&gt;remove-unused-deps.ts&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That last rule is why the template sets &lt;code&gt;keepAll&lt;/code&gt; on &lt;code&gt;@angular/core&lt;/code&gt;. Imagine two remotes on different Angular patch versions, where only one of them imports &lt;code&gt;@angular/core/rxjs-interop&lt;/code&gt;. The other remote drops that sub-path from its build. At runtime, the page can end up loading &lt;code&gt;@angular/core&lt;/code&gt; from one version and &lt;code&gt;rxjs-interop&lt;/code&gt; from another, splitting Angular across versions. The &lt;a href="https://native-federation.com/docs/v4/core/configuration/" rel="noopener noreferrer"&gt;Core configuration docs&lt;/a&gt; warn about exactly this, and &lt;code&gt;keepAll&lt;/code&gt; on framework libraries is the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means for remotes:&lt;/strong&gt; a library used only by a remote's own startup code, in &lt;code&gt;main.ts&lt;/code&gt; or &lt;code&gt;bootstrap.ts&lt;/code&gt;, isn't shared by that remote. It still works, because it's bundled into the remote instead. But if you expected the host and the remote to share it, check the remote's &lt;code&gt;remoteEntry.json&lt;/code&gt; rather than assuming.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when you build
&lt;/h2&gt;

&lt;p&gt;With the config understood, here's what actually runs when you type &lt;code&gt;ng build&lt;/code&gt;. The order comes from the Angular adapter's &lt;a href="https://native-federation.com/docs/v4/angular-adapter/builder/" rel="noopener noreferrer"&gt;builder documentation&lt;/a&gt; and its &lt;a href="https://github.com/native-federation/angular-adapter/blob/main/src/builders/build/builder.ts" rel="noopener noreferrer"&gt;source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1sq9346snb0q4bh3v4bk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1sq9346snb0q4bh3v4bk.png" alt="The build pipeline: 1. read federation.config.mjs and work out the shared list, 2. find which shared libraries are actually used, 3. reuse each shared library from the cache or build it as its own file, 4. build the exposed modules, 5. write remoteEntry.json and importmap.json, 6. Angular's ApplicationBuilder builds your app with shared libraries left out, producing dist/your-app/browser." width="784" height="2382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the config.&lt;/strong&gt; &lt;code&gt;withNativeFederation()&lt;/code&gt; fills in defaults, applies the skip list and works out the shared list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find what's used.&lt;/strong&gt; The &lt;code&gt;ignoreUnusedDeps&lt;/code&gt; scan from the previous section trims the shared list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the shared libraries.&lt;/strong&gt; Each one becomes its own file. Built libraries are cached in &lt;code&gt;node_modules/.cache/native-federation/&amp;lt;project&amp;gt;&lt;/code&gt;, so the next build can reuse them. The cache refreshes itself when installed versions or sharing settings change (&lt;a href="https://native-federation.com/docs/v4/core/caching/" rel="noopener noreferrer"&gt;Core caching&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build the exposed modules.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write &lt;code&gt;remoteEntry.json&lt;/code&gt; and &lt;code&gt;importmap.json&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand off to Angular.&lt;/strong&gt; The adapter is a thin wrapper around Angular's own &lt;code&gt;ApplicationBuilder&lt;/code&gt;. Angular builds your app as usual, with the shared libraries marked as externals so they stay out of your bundle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two practical notes. If the Angular build fails, &lt;strong&gt;no federation files are written at all&lt;/strong&gt;, so a failing build can never ship a half-updated &lt;code&gt;remoteEntry.json&lt;/code&gt;. And if a build ever looks stale, deleting &lt;code&gt;node_modules/.cache/native-federation&lt;/code&gt; forces a clean rebuild of the shared libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The output: remoteEntry.json and importmap.json
&lt;/h2&gt;

&lt;h3&gt;
  
  
  remoteEntry.json: what this app offers and needs
&lt;/h3&gt;

&lt;p&gt;This is the file other apps read. Its shape is defined in &lt;a href="https://github.com/native-federation/native-federation-core/blob/main/src/lib/domain/core/federation-info.contract.ts" rel="noopener noreferrer"&gt;federation-info.contract.ts&lt;/a&gt; and written by &lt;a href="https://github.com/native-federation/native-federation-core/blob/main/src/lib/core/output/write-federation-info.ts" rel="noopener noreferrer"&gt;write-federation-info.ts&lt;/a&gt;. A simplified example (real file names include content hashes and will differ):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mfe1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exposes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./Component"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"outFileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Component-HASH.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shared"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"packageName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@angular/core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"outFileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"angular-core-HASH.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20.1.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"requiredVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^20.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"singleton"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"strictVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every setting from the config shows up here. &lt;code&gt;version&lt;/code&gt; is what this app was built with, and &lt;code&gt;requiredVersion&lt;/code&gt; is what it accepts. &lt;code&gt;singleton&lt;/code&gt; and &lt;code&gt;strictVersion&lt;/code&gt; tell the browser how strict to be. The &lt;code&gt;$version: "v4"&lt;/code&gt; marker tells runtimes which manifest format they're reading. With &lt;code&gt;denseChunking&lt;/code&gt; turned on, there's also a &lt;code&gt;chunks&lt;/code&gt; section listing the split code files.&lt;/p&gt;

&lt;h3&gt;
  
  
  importmap.json: this app's own view
&lt;/h3&gt;

&lt;p&gt;The build also writes an &lt;code&gt;importmap.json&lt;/code&gt; (&lt;a href="https://github.com/native-federation/native-federation-core/blob/main/src/lib/core/output/write-import-map.ts" rel="noopener noreferrer"&gt;write-import-map.ts&lt;/a&gt;). It maps each shared library to the file that holds it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@angular/core"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"angular-core-HASH.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rxjs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rxjs-HASH.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://native-federation.com/docs/v4/mental-model/" rel="noopener noreferrer"&gt;mental model docs&lt;/a&gt; describe it as "this project's view of where its externals live." The key word is &lt;em&gt;this project's&lt;/em&gt;. It only knows about one app. The import map that actually runs in the browser is built later, by the runtime, from the &lt;code&gt;remoteEntry.json&lt;/code&gt; files of the host and every remote together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static and dynamic remotes
&lt;/h2&gt;

&lt;p&gt;This is the part that surprises people coming from webpack Module Federation: &lt;strong&gt;a Native Federation host never looks at its remotes during the build.&lt;/strong&gt; It doesn't fetch them, check them, or link against them. The only question is &lt;em&gt;where the host finds the remote URLs at runtime&lt;/em&gt;, and the &lt;code&gt;ng add&lt;/code&gt; schematic gives you two choices through &lt;code&gt;--type&lt;/code&gt; (&lt;a href="https://native-federation.com/docs/v4/angular-adapter/getting-started/" rel="noopener noreferrer"&gt;Angular adapter getting started&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F27fayxsswkist1cuxhh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F27fayxsswkist1cuxhh6.png" alt="Where the host finds remote URLs. At build time the host build never fetches or checks remotes, and each remote build publishes its own remoteEntry.json. With type host, URLs are written in main.ts and changing them means rebuilding the host. With dynamic-host, URLs live in federation.manifest.json and changing them means editing one file. Remotes can also be added later at runtime with initRemoteEntry, for example plugins. In the browser, the runtime fetches each remoteEntry.json and builds the final import map." width="799" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static remotes (&lt;code&gt;--type host&lt;/code&gt;).&lt;/strong&gt; The remote URLs are written straight into &lt;code&gt;main.ts&lt;/code&gt; as an object. The &lt;a href="https://github.com/native-federation/angular-adapter/blob/main/src/schematics/init/steps/make-main-async.ts" rel="noopener noreferrer"&gt;schematic source&lt;/a&gt; generates something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;initFederation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mfe1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4201/remoteEntry.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hostRemoteEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./remoteEntry.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's simple, but the URLs are now part of your host's code. Moving a remote to a new URL, or deploying the same host to test and production, means rebuilding the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic remotes (&lt;code&gt;--type dynamic-host&lt;/code&gt;, the recommended default).&lt;/strong&gt; The URLs live in a &lt;code&gt;federation.manifest.json&lt;/code&gt; in your &lt;code&gt;public/&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mfe1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4201/remoteEntry.json"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.ts&lt;/code&gt; just passes the file name, &lt;code&gt;initFederation('federation.manifest.json', ...)&lt;/code&gt;, and the manifest is fetched when the app starts. The same host build can run in every environment. Only the manifest changes, and it can even be generated by your deployment pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remotes discovered later.&lt;/strong&gt; Sometimes nobody knows the full list of remotes up front, as in a plugin system where customers enable features. The Orchestrator handles this with &lt;code&gt;initRemoteEntry()&lt;/code&gt;, which adds a remote after the app has started (&lt;a href="https://native-federation.com/docs/v4/orchestrator/getting-started/" rel="noopener noreferrer"&gt;Orchestrator getting started&lt;/a&gt;). It only ever adds to the import map and never changes decisions already made.&lt;/p&gt;

&lt;p&gt;Two more details from that generated &lt;code&gt;main.ts&lt;/code&gt; are worth noticing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The host publishes its own &lt;code&gt;remoteEntry.json&lt;/code&gt;.&lt;/strong&gt; Every generated &lt;code&gt;main.ts&lt;/code&gt; passes &lt;code&gt;hostRemoteEntry: { url: './remoteEntry.json' }&lt;/code&gt;. That's the host's own build output, and at runtime the host's versions win whenever a library appears in both the host and a remote (&lt;a href="https://native-federation.com/docs/v4/orchestrator/configuration/" rel="noopener noreferrer"&gt;Orchestrator configuration&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A remote can run on its own.&lt;/strong&gt; A remote's &lt;code&gt;main.ts&lt;/code&gt; calls &lt;code&gt;initFederation({}, ...)&lt;/code&gt; with an empty list. It acts as its own host, which is why you can open a remote in the browser and work on it alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where version resolution fits
&lt;/h2&gt;

&lt;p&gt;Everything in this article happens at build time, and the build never &lt;em&gt;chooses&lt;/em&gt; a version. It only &lt;strong&gt;records&lt;/strong&gt; them: which version each app has, which range it accepts, and how strict it wants to be. The choosing happens in the browser, when the runtime reads all the &lt;code&gt;remoteEntry.json&lt;/code&gt; files together. Part 4 looks at how the Classic Runtime made that choice, and why it often ended up loading two copies of Angular. Part 5 shows how the Orchestrator does it properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;federation.config.mjs&lt;/code&gt; decides &lt;em&gt;what&lt;/em&gt; is shared and &lt;em&gt;how strictly&lt;/em&gt;. By default, everything is a strict singleton.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;skip&lt;/code&gt; doesn't remove a library. It just stops it from being shared.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;ignoreUnusedDeps&lt;/code&gt; (on by default), a host is scanned from &lt;code&gt;main.ts&lt;/code&gt; through &lt;code&gt;bootstrap.ts&lt;/code&gt;, while a remote is scanned from its exposed modules only.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;includeSecondaries: { keepAll: true }&lt;/code&gt; on framework libraries, so sub-paths can't split a framework across versions.&lt;/li&gt;
&lt;li&gt;The build writes &lt;code&gt;remoteEntry.json&lt;/code&gt; (what this app offers and needs) and &lt;code&gt;importmap.json&lt;/code&gt; (this app's own view).&lt;/li&gt;
&lt;li&gt;A host never looks at remotes during the build. Prefer &lt;code&gt;dynamic-host&lt;/code&gt; so one build can serve every environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Series roadmap
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/sakthicodes22/the-mental-model-revisited-why-native-federation-exists-and-what-changed-in-v4-1klg"&gt;&lt;strong&gt;The Mental Model, Revisited&lt;/strong&gt;&lt;/a&gt;: why Native Federation exists and what changed in v4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anatomy of the v4 Package Graph&lt;/strong&gt;: Core, Adapters, Runtime and Orchestrator as separate layers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build-Time, End to End&lt;/strong&gt;: from &lt;code&gt;federation.config&lt;/code&gt; to &lt;code&gt;remoteEntry.json&lt;/code&gt; &lt;em&gt;(this article)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Classic Runtime&lt;/strong&gt;: what it got right, and where it hits its limit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Orchestrator&lt;/strong&gt;: how version resolution and caching work, in depth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Drift and Resolution Strategy&lt;/strong&gt;: keeping many independently deployed apps in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v3 vs. v4&lt;/strong&gt;: a detailed comparison and a practical migration plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference Architecture&lt;/strong&gt;: running Native Federation v4 in a regulated enterprise&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Official documentation&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/mental-model/" rel="noopener noreferrer"&gt;Mental model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/architecture/" rel="noopener noreferrer"&gt;Architecture overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/core/getting-started/" rel="noopener noreferrer"&gt;Core: getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/core/configuration/" rel="noopener noreferrer"&gt;Core: configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/core/caching/" rel="noopener noreferrer"&gt;Core: caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/angular-adapter/getting-started/" rel="noopener noreferrer"&gt;Angular adapter: getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/angular-adapter/builder/" rel="noopener noreferrer"&gt;Angular adapter: builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/orchestrator/getting-started/" rel="noopener noreferrer"&gt;Orchestrator: getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/orchestrator/configuration/" rel="noopener noreferrer"&gt;Orchestrator: configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Source code&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/native-federation/native-federation-core" rel="noopener noreferrer"&gt;native-federation-core&lt;/a&gt;: see &lt;code&gt;src/lib/config&lt;/code&gt; for the config helpers and the unused-dependency scan, and &lt;code&gt;src/lib/core/output&lt;/code&gt; for the files the build writes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/native-federation/angular-adapter" rel="noopener noreferrer"&gt;angular-adapter&lt;/a&gt;: see &lt;code&gt;src/builders/build&lt;/code&gt; for the builder and &lt;code&gt;src/schematics/init&lt;/code&gt; for what &lt;code&gt;ng add&lt;/code&gt; generates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Articles by the maintainers&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Auke van Oostenbrugge, &lt;a href="https://medium.com/@auke997/tweaking-native-federation-part-ii-sharing-externals-a4e1d3711230" rel="noopener noreferrer"&gt;Tweaking native-federation, Part II: Sharing externals&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Auke van Oostenbrugge, &lt;a href="https://medium.com/@auke997/part-ii-composing-your-own-native-federation-orchestrator-7fac2d0ea62e" rel="noopener noreferrer"&gt;Part II: Composing your own native-federation orchestrator&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Next in this series: **The Classic Runtime&lt;/em&gt;&lt;em&gt;, what it got right, and where it hits its limit.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>microfrontends</category>
      <category>nativefederation</category>
      <category>angular</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Anatomy of the v4 Package Graph - Core, Adapters, Runtime, and Orchestrator</title>
      <dc:creator>Sakthikumaran Navakumar</dc:creator>
      <pubDate>Wed, 30 Sep 2026 01:31:47 +0000</pubDate>
      <link>https://dev.to/sakthicodes22/anatomy-of-the-v4-package-graph-core-adapters-runtime-and-orchestrator-36b8</link>
      <guid>https://dev.to/sakthicodes22/anatomy-of-the-v4-package-graph-core-adapters-runtime-and-orchestrator-36b8</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 2 of an 8-part series on Native Federation v4 for enterprise architecture teams. New to the series? Start with &lt;a href="https://dev.to/sakthicodes22/the-mental-model-revisited-why-native-federation-exists-and-what-changed-in-v4-1klg"&gt;Part 1: The Mental Model, Revisited&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/sakthicodes22/the-mental-model-revisited-why-native-federation-exists-and-what-changed-in-v4-1klg"&gt;Part 1&lt;/a&gt;, I discussed how Native Federation v4 is a platform, not just a plugin. The main evidence was that v4 split the project into several separate packages, each with its own version.&lt;/p&gt;

&lt;p&gt;🤖 A note on this article: I used Claude to help reformat and structure the content to make it clearer and more presentable for publication.&lt;/p&gt;

&lt;p&gt;But splitting code into more packages doesn't prove much on its own. The split only matters if each package has a clear job, and if you can upgrade or replace one package without breaking the others.&lt;/p&gt;

&lt;p&gt;This article tests that. It walks through the four layers of Native Federation v4 (Core, Adapters, the Classic Runtime and the Orchestrator), explains what each one is responsible for, and shows the one contract that connects them. The rest of the series builds on this map.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few terms first
&lt;/h2&gt;

&lt;p&gt;If you're new to micro frontends, these terms appear throughout the article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Host:&lt;/strong&gt; the main application (often called the "shell") that loads other applications into itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote:&lt;/strong&gt; a separately built and deployed application that the host loads at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared dependency:&lt;/strong&gt; a library, like Angular or RxJS, that the host and remotes all use. Ideally the browser downloads it once and everyone reuses it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundler:&lt;/strong&gt; the tool that turns your source code into files the browser can run, such as esbuild or webpack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import map:&lt;/strong&gt; a standard browser feature. It's a small JSON block on the page that tells the browser where to find a module. For example: "when code asks for &lt;code&gt;rxjs&lt;/code&gt;, load it from this URL."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic versioning (semver):&lt;/strong&gt; the &lt;code&gt;major.minor.patch&lt;/code&gt; version format. A range like &lt;code&gt;^18.0.0&lt;/code&gt; means "any 18.x version is fine."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Singleton:&lt;/strong&gt; a library that must exist only once on the page. Angular is the classic example: two copies of Angular on one page cause errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  From one plugin to four repositories
&lt;/h2&gt;

&lt;p&gt;Native Federation used to live inside the &lt;code&gt;angular-architects/module-federation-plugin&lt;/code&gt; repository, alongside the same team's webpack Module Federation tools. For v4, the maintainers moved it into its own GitHub organization and split it into four repositories (&lt;a href="https://native-federation.com/docs/v4/v3-vs-v4/" rel="noopener noreferrer"&gt;v3 vs v4&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;native-federation-core:&lt;/strong&gt; the core logic. It doesn't depend on any framework or bundler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;esbuild-adapter:&lt;/strong&gt; connects the core to the esbuild bundler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;angular-adapter:&lt;/strong&gt; connects the core to Angular's build tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;orchestrator:&lt;/strong&gt; loads remotes in the browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each one below, we'll ask the same question: what does it own, and what does it deliberately leave to the others?&lt;/p&gt;

&lt;h2&gt;
  
  
  The four layers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core: runs at build time, and only at build time
&lt;/h3&gt;

&lt;p&gt;The core package is &lt;code&gt;@softarc/native-federation&lt;/code&gt;. It doesn't know about Angular, esbuild, or how a browser loads a remote. The official &lt;a href="https://native-federation.com/docs/v4/architecture/" rel="noopener noreferrer"&gt;architecture overview&lt;/a&gt; describes its job in one line: it "normalizes the federation config, bundles shared dependencies and exposed modules, and emits &lt;code&gt;remoteEntry.json&lt;/code&gt; + the import map."&lt;/p&gt;

&lt;p&gt;That job has three parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It defines the configuration.&lt;/strong&gt; Every federated project, whatever framework it uses, is described with the same functions (&lt;a href="https://native-federation.com/docs/v4/core/configuration/" rel="noopener noreferrer"&gt;Core configuration&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;withNativeFederation()&lt;/code&gt; holds the whole configuration: the app's &lt;code&gt;name&lt;/code&gt;, the modules it &lt;code&gt;exposes&lt;/code&gt; to other apps, and the libraries it &lt;code&gt;shared&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fromPackageJson()&lt;/code&gt; is the recommended way to list shared libraries. It shares everything in your &lt;code&gt;package.json&lt;/code&gt;, and lets you handle exceptions with &lt;code&gt;.skip()&lt;/code&gt;, &lt;code&gt;.override()&lt;/code&gt; and &lt;code&gt;.patch()&lt;/code&gt;. The older &lt;code&gt;shareAll()&lt;/code&gt; still works.&lt;/li&gt;
&lt;li&gt;Each shared library gets a few settings that matter later, when the browser decides which version to load: &lt;code&gt;singleton&lt;/code&gt; (only one copy allowed), &lt;code&gt;strictVersion&lt;/code&gt; (versions must match), &lt;code&gt;requiredVersion&lt;/code&gt; (the acceptable range, where &lt;code&gt;'auto'&lt;/code&gt; reads it from &lt;code&gt;package.json&lt;/code&gt;) and &lt;code&gt;includeSecondaries&lt;/code&gt; (whether to also share sub-paths like &lt;code&gt;@angular/common/http&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sharedMappings&lt;/code&gt; handles monorepos, where internal libraries are shared through TypeScript path aliases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. It plans the build, but doesn't run the bundler.&lt;/strong&gt; Core decides &lt;em&gt;what&lt;/em&gt; to build. Your app is built without the shared libraries inside it, and each shared library is built separately, so the browser can download it once and reuse it. Core never compiles your code itself. It hands that work to an adapter (&lt;a href="https://native-federation.com/docs/v4/core/getting-started/" rel="noopener noreferrer"&gt;Core getting started&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It writes the manifest.&lt;/strong&gt; Core's most important output is a file called &lt;code&gt;remoteEntry.json&lt;/code&gt;. Think of it as a remote's label. It lists what the remote exposes and which files contain it. It also lists every shared library, with the version it was built with, the version range it accepts, and its &lt;code&gt;singleton&lt;/code&gt; and &lt;code&gt;strictVersion&lt;/code&gt; settings.&lt;/p&gt;

&lt;p&gt;v4 adds two optional settings that keep this file small in large projects. &lt;code&gt;features.denseChunking&lt;/code&gt; groups split code files by bundle, and &lt;code&gt;features.denseExternals&lt;/code&gt; groups all the entry points of one library into a single entry (&lt;a href="https://native-federation.com/docs/v4/core/configuration/" rel="noopener noreferrer"&gt;Core configuration&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;What Core does &lt;em&gt;not&lt;/em&gt; do matters just as much. It doesn't turn TypeScript into JavaScript, and it doesn't decide which library version the browser loads. Because its job is small and clear, it can stay stable while everything around it changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adapters: connect a specific tool to Core
&lt;/h3&gt;

&lt;p&gt;Core works with any bundler in principle, but something has to connect it to a real one. That's all an adapter does. There are two kinds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bundler adapters&lt;/strong&gt; connect Core to one bundler. The esbuild adapter, &lt;code&gt;@softarc/native-federation-esbuild&lt;/code&gt;, is the reference one.&lt;/p&gt;

&lt;p&gt;This adapter also solves a common real-world problem: CommonJS. Browsers run modern JavaScript modules (ES modules, or ESM). Some popular libraries, React most famously, are still published in the older CommonJS format. The adapter includes a plugin, &lt;code&gt;@chialab/esbuild-plugin-commonjs&lt;/code&gt;, that converts them, and its default React setup turns it on for you. The docs also cover libraries that still don't convert cleanly (&lt;a href="https://native-federation.com/docs/v4/adapters/esbuild/getting-started/" rel="noopener noreferrer"&gt;esbuild adapter getting started&lt;/a&gt;). If your project uses older CommonJS libraries, this is where you'll run into them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework adapters&lt;/strong&gt; make Native Federation feel like a normal part of a framework's tools. The Angular adapter is the most mature. It plugs into &lt;code&gt;angular.json&lt;/code&gt; and hands the actual build to Angular's own &lt;code&gt;ApplicationBuilder&lt;/code&gt;. So you get every build improvement the Angular team ships, without a separate build process.&lt;/p&gt;

&lt;p&gt;You set it up with &lt;code&gt;ng add&lt;/code&gt;. The &lt;code&gt;--type&lt;/code&gt; option decides what you're creating (&lt;a href="https://native-federation.com/docs/v4/angular-adapter/getting-started/" rel="noopener noreferrer"&gt;Angular adapter getting started&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;remote&lt;/code&gt;: a micro frontend.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;host&lt;/code&gt;: a shell for one environment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dynamic-host&lt;/code&gt;: a shell that reads its list of remotes at runtime. This is the recommended default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The schematic updates &lt;code&gt;angular.json&lt;/code&gt;, moves your startup code into a &lt;code&gt;bootstrap.ts&lt;/code&gt; file, and creates a &lt;code&gt;federation.config.mjs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two details help with planning. First, the Angular adapter's version numbers follow Angular's, so keep them on the same major version. On Angular 20 and 21, v4 is published as &lt;code&gt;@angular-architects/native-federation-v4&lt;/code&gt;. From Angular 22, it's back under the original name, &lt;code&gt;@angular-architects/native-federation&lt;/code&gt; (&lt;a href="https://native-federation.com/docs/v4/angular-adapter/getting-started/" rel="noopener noreferrer"&gt;Angular adapter getting started&lt;/a&gt;). Second, an adapter only translates between a tool and Core. When the Angular adapter changes, it's usually because Angular's build tools changed, not because Core did.&lt;/p&gt;

&lt;p&gt;Adapters are the layer most teams work with every day: running the schematic, editing the config, and upgrading with each Angular release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classic Runtime: the original browser loader
&lt;/h3&gt;

&lt;p&gt;The Classic Runtime, &lt;code&gt;@softarc/native-federation-runtime&lt;/code&gt;, is what most existing projects still use in the browser. Its design was simple. In the words of the docs, it "read &lt;code&gt;remoteEntry.json&lt;/code&gt; files, merged them into one ES module import map, injected it into the DOM, and resolved &lt;code&gt;loadRemoteModule()&lt;/code&gt; calls against it" (&lt;a href="https://native-federation.com/docs/v4/runtime/" rel="noopener noreferrer"&gt;Runtime docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In practice, you call &lt;code&gt;initFederation()&lt;/code&gt; in the host before the app starts, then call &lt;code&gt;loadRemoteModule()&lt;/code&gt; whenever you need a remote. It uses a small library called &lt;code&gt;es-module-shims&lt;/code&gt; so import maps work reliably, which is why the startup code is split into &lt;code&gt;main.ts&lt;/code&gt; and &lt;code&gt;bootstrap.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The catch is that it doesn't negotiate versions. It has no semver resolution and no caching between page loads (&lt;a href="https://native-federation.com/docs/v4/orchestrator/" rel="noopener noreferrer"&gt;Orchestrator docs&lt;/a&gt;). When two remotes want different versions of a library, the docs say they "simply each kept their own" (&lt;a href="https://native-federation.com/docs/v4/runtime/" rel="noopener noreferrer"&gt;Runtime docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For a small utility library, that just costs extra download time. For a framework, it breaks things. Say the host is built with Angular 18.1.0 and a remote with Angular 18.1.1. The page ends up with two copies of Angular, and you get &lt;code&gt;NG0203&lt;/code&gt; errors (Angular's "injection context" error). Many teams have hit this in production.&lt;/p&gt;

&lt;p&gt;The Classic Runtime is now &lt;strong&gt;deprecated and end-of-life&lt;/strong&gt;. The last version is &lt;code&gt;4.1.2&lt;/code&gt;, and its npm page tells users to switch to the Orchestrator (&lt;a href="https://native-federation.com/docs/v4/runtime/" rel="noopener noreferrer"&gt;Runtime docs&lt;/a&gt;). Existing projects keep working, but there will be no more fixes. Part 4 looks at why its design hit a limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orchestrator: the new browser loader
&lt;/h3&gt;

&lt;p&gt;The Orchestrator, &lt;code&gt;@softarc/native-federation-orchestrator&lt;/code&gt;, reads the same &lt;code&gt;remoteEntry.json&lt;/code&gt; files as the Classic Runtime. The difference is that it actually decides which version of each library to load (&lt;a href="https://native-federation.com/docs/v4/orchestrator/" rel="noopener noreferrer"&gt;Orchestrator docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You pass it around instead of importing it.&lt;/strong&gt; &lt;code&gt;initFederation(manifest, options)&lt;/code&gt; gives you back an object that includes &lt;code&gt;loadRemoteModule&lt;/code&gt;, &lt;code&gt;load&lt;/code&gt; and &lt;code&gt;initRemoteEntry&lt;/code&gt;. You pass that object into your app rather than importing a global function. In Angular, the docs do this with an &lt;code&gt;InjectionToken&lt;/code&gt;, so it works through normal dependency injection. The options control logging, storage and how strict version checks are (&lt;a href="https://native-federation.com/docs/v4/orchestrator/getting-started/" rel="noopener noreferrer"&gt;Orchestrator getting started&lt;/a&gt;; &lt;a href="https://native-federation.com/docs/v4/orchestrator/configuration/" rel="noopener noreferrer"&gt;configuration&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It remembers what it has already loaded.&lt;/strong&gt; The Orchestrator keeps four caches (&lt;a href="https://native-federation.com/docs/v4/orchestrator/architecture/" rel="noopener noreferrer"&gt;architecture&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where each remote lives and what it exposes&lt;/li&gt;
&lt;li&gt;shared libraries, grouped by package&lt;/li&gt;
&lt;li&gt;private libraries that belong to a single remote&lt;/li&gt;
&lt;li&gt;shared code chunks (new in v4)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default these caches are kept in memory. You can save them with &lt;code&gt;sessionStorageEntry&lt;/code&gt; or &lt;code&gt;localStorageEntry&lt;/code&gt;, so the next page load doesn't start from scratch (&lt;a href="https://native-federation.com/docs/v4/orchestrator/getting-started/" rel="noopener noreferrer"&gt;getting started&lt;/a&gt;). This helps most with server-rendered sites that reload the whole page on every click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It picks versions in three steps&lt;/strong&gt; (&lt;a href="https://native-federation.com/docs/v4/orchestrator/version-resolver/" rel="noopener noreferrer"&gt;version resolver&lt;/a&gt;):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sort.&lt;/strong&gt; Libraries not marked as singletons skip the negotiation, and each remote simply gets its own copy. Singletons go into a group called a &lt;em&gt;share scope&lt;/em&gt;. There's a global scope by default. You can create named scopes, such as &lt;code&gt;"team-a"&lt;/code&gt;, so one team's libraries never compete with another team's. There's also a special &lt;code&gt;"strict"&lt;/code&gt; scope, where every requested version is kept exactly as-is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose.&lt;/strong&gt; Within each scope, one version of each library wins. If the host declares a version, the host always wins. Otherwise, by default, the Orchestrator picks the version that causes the fewest extra downloads. You can switch that to "pick the newest version" with the &lt;code&gt;latestSharedExternal&lt;/code&gt; setting. Remotes that asked for an incompatible version are then handled in one of two ways. With &lt;code&gt;strictVersion: false&lt;/code&gt; (the default), they use the winning version anyway and a warning is logged. With &lt;code&gt;strictVersion: true&lt;/code&gt;, they download their own private copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the import map.&lt;/strong&gt; The winning versions go into the import map's &lt;code&gt;imports&lt;/code&gt; section. Private copies go into &lt;code&gt;scopes&lt;/code&gt;, a per-remote section. Optional security hashes go into &lt;code&gt;integrity&lt;/code&gt; (&lt;a href="https://native-federation.com/docs/v4/orchestrator/architecture/" rel="noopener noreferrer"&gt;architecture&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If more remotes show up after the app starts, &lt;code&gt;initRemoteEntry()&lt;/code&gt; adds them. It only ever adds to the import map. It never changes a decision that was already made.&lt;/p&gt;

&lt;p&gt;Two design choices are worth knowing about early (&lt;a href="https://native-federation.com/docs/v4/orchestrator/version-resolver/" rel="noopener noreferrer"&gt;version resolver&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strictness is all or nothing.&lt;/strong&gt; You turn on &lt;code&gt;strict.strictExternalCompatibility&lt;/code&gt; once, in the options for &lt;code&gt;initFederation()&lt;/code&gt;. When it's on, every version mismatch throws an error instead of a warning. You can't be strict about Angular and relaxed about a small utility library in the same scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The host's version always wins.&lt;/strong&gt; If a remote ships a newer, compatible patch, the page still loads the host's version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither is a bug. They are deliberate choices, and they affect how you should set up your share scopes. Parts 5 and 6 cover this in depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one contract that connects everything
&lt;/h2&gt;

&lt;p&gt;Core and the adapters run at build time, before you deploy. The Classic Runtime and the Orchestrator run later, in the user's browser. The only thing passed from one side to the other is the manifest, &lt;code&gt;remoteEntry.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The architecture overview puts it this way: "the Core and Runtime speak a simple contract — &lt;code&gt;remoteEntry.json&lt;/code&gt; plus an import map. Everything else (which bundler, which framework, which runtime) is swappable" (&lt;a href="https://native-federation.com/docs/v4/architecture/" rel="noopener noreferrer"&gt;architecture overview&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is the most important idea in this article. Core doesn't need to know which runtime will read its output. It just writes a manifest in an agreed format. The runtimes don't need to know about each other, or about which Core version wrote a manifest. They just read that format.&lt;/p&gt;

&lt;p&gt;That's why the split into separate packages actually works. In practice, it means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can upgrade Core without every team having to change its runtime at the same time.&lt;/li&gt;
&lt;li&gt;During a migration, one app can use the Classic Runtime while another uses the Orchestrator, both reading manifests from the same Core version.&lt;/li&gt;
&lt;li&gt;The Orchestrator can load v3 and v4 remotes on the same page, because both follow the same contract (&lt;a href="https://native-federation.com/docs/v4/orchestrator/" rel="noopener noreferrer"&gt;Orchestrator docs&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part 7's migration plan depends on exactly this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference: which package does what
&lt;/h2&gt;

&lt;p&gt;Keep this table handy. It's useful mid-migration, when you're looking at a lockfile and trying to work out what you're actually running.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Responsible for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@softarc/native-federation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Core&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;Configuration, build planning, the &lt;code&gt;remoteEntry.json&lt;/code&gt; format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@softarc/native-federation-esbuild&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bundler adapter&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;Connecting esbuild, converting CommonJS libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@angular-architects/native-federation&lt;/code&gt; (Angular 22+) / &lt;code&gt;-v4&lt;/code&gt; (Angular 20–21)&lt;/td&gt;
&lt;td&gt;Framework adapter&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;Connecting Angular's build tools, schematics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@softarc/native-federation-runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runtime (classic)&lt;/td&gt;
&lt;td&gt;Deprecated, end-of-life&lt;/td&gt;
&lt;td&gt;Loading remotes with one merged import map, no version negotiation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@softarc/native-federation-orchestrator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runtime (v4)&lt;/td&gt;
&lt;td&gt;Active, recommended&lt;/td&gt;
&lt;td&gt;Loading remotes, choosing versions, share scopes, caching&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Check your own project
&lt;/h2&gt;

&lt;p&gt;Before Part 3 looks at what happens during the build, take five minutes to check your own project. Open &lt;code&gt;package.json&lt;/code&gt; or your lockfile and look for these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core:&lt;/strong&gt; which version you're on, and how far behind the latest release it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adapter:&lt;/strong&gt; which adapter you use, and whether its major version matches your Angular version. On Angular 20 or 21, check whether you're on the &lt;code&gt;-v4&lt;/code&gt; package or still on v3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; whether you use the Classic Runtime or the Orchestrator. If you see &lt;code&gt;@softarc/native-federation-runtime&lt;/code&gt;, you depend on a package that no longer gets fixes. Plan the move now, not after the next incident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config file:&lt;/strong&gt; whether your config is still &lt;code&gt;federation.config.js&lt;/code&gt; using &lt;code&gt;require()&lt;/code&gt; and &lt;code&gt;module.exports&lt;/code&gt;. v4 expects &lt;code&gt;federation.config.mjs&lt;/code&gt;, written as an ES module, so the old style means your setup predates v4 (&lt;a href="https://native-federation.com/docs/v4/migration/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This check sounds basic, but it catches the most common problem teams hit when adopting v4. Usually the hard part isn't the concepts. It's finding out halfway through a migration that one layer is several versions behind where everyone thought it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Series roadmap
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/sakthicodes22/the-mental-model-revisited-why-native-federation-exists-and-what-changed-in-v4-1klg"&gt;&lt;strong&gt;The Mental Model, Revisited&lt;/strong&gt;&lt;/a&gt;: why Native Federation exists and what changed in v4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anatomy of the v4 Package Graph&lt;/strong&gt;: Core, Adapters, Runtime and Orchestrator as separate layers &lt;em&gt;(this article)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build-Time, End to End&lt;/strong&gt;: what Core and the adapters actually do to your code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Classic Runtime&lt;/strong&gt;: what it got right, and where it hits its limit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Orchestrator&lt;/strong&gt;: how version resolution and caching work, in depth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Drift and Resolution Strategy&lt;/strong&gt;: keeping many independently deployed apps in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v3 vs. v4&lt;/strong&gt;: a detailed comparison and a practical migration plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference Architecture&lt;/strong&gt;: running Native Federation v4 in a regulated enterprise&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Official documentation&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/architecture/" rel="noopener noreferrer"&gt;Architecture overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/core/getting-started/" rel="noopener noreferrer"&gt;Core: getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/core/configuration/" rel="noopener noreferrer"&gt;Core: configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/adapters/esbuild/getting-started/" rel="noopener noreferrer"&gt;esbuild adapter: getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/angular-adapter/getting-started/" rel="noopener noreferrer"&gt;Angular adapter: getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/runtime/" rel="noopener noreferrer"&gt;Classic Runtime (legacy)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/orchestrator/" rel="noopener noreferrer"&gt;Orchestrator overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/orchestrator/getting-started/" rel="noopener noreferrer"&gt;Orchestrator: getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/orchestrator/configuration/" rel="noopener noreferrer"&gt;Orchestrator: configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/orchestrator/architecture/" rel="noopener noreferrer"&gt;Orchestrator: architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/orchestrator/version-resolver/" rel="noopener noreferrer"&gt;Orchestrator: version resolver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/v3-vs-v4/" rel="noopener noreferrer"&gt;v3 vs v4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://native-federation.com/docs/v4/migration/" rel="noopener noreferrer"&gt;Migrating to v4&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Articles by the maintainers&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Auke van Oostenbrugge, &lt;a href="https://medium.com/@auke997/part-ii-composing-your-own-native-federation-orchestrator-7fac2d0ea62e" rel="noopener noreferrer"&gt;Part II: Composing your own native-federation orchestrator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Auke van Oostenbrugge, &lt;a href="https://medium.com/@auke997/part-iii-improving-cache-utilization-in-native-federation-1e4b696f3426" rel="noopener noreferrer"&gt;Part III: Improving Cache Utilization in Native Federation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Auke van Oostenbrugge, &lt;a href="https://medium.com/@auke997/tweaking-native-federation-part-ii-sharing-externals-a4e1d3711230" rel="noopener noreferrer"&gt;Tweaking native-federation, Part II: Sharing externals&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Next in this series: **Build-Time, End to End&lt;/em&gt;&lt;em&gt;, what Core and the adapters actually do to your code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>microfrontends</category>
      <category>nativefederation</category>
      <category>angular</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Native Federation - Deep Dive - Part 1/8 - The Mental Model, Why Native Federation Exists and What Changed in v4</title>
      <dc:creator>Sakthikumaran Navakumar</dc:creator>
      <pubDate>Sat, 19 Sep 2026 01:05:01 +0000</pubDate>
      <link>https://dev.to/sakthicodes22/the-mental-model-revisited-why-native-federation-exists-and-what-changed-in-v4-1klg</link>
      <guid>https://dev.to/sakthicodes22/the-mental-model-revisited-why-native-federation-exists-and-what-changed-in-v4-1klg</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 1 of an 8-part series on Native Federation - A deep dive.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;🤖 A note on this article: I used Claude to help reformat and structure the content to make it clearer and more presentable for publication.&lt;/p&gt;

&lt;p&gt;If you've shipped a production micro-frontend system on webpack Module Federation, you already know the mental model: a &lt;strong&gt;host&lt;/strong&gt; application lazy-loads code it never saw at compile time, from a &lt;strong&gt;remote&lt;/strong&gt; it doesn't own, and the two negotiate &lt;strong&gt;shared dependencies&lt;/strong&gt; at runtime so you're not shipping three copies of React down the wire. Native Federation didn't invent that model — it inherited it deliberately, because the model was never the problem. The &lt;em&gt;implementation&lt;/em&gt; was.&lt;/p&gt;

&lt;p&gt;This article isn't going to re-teach you what a remote is. It's going to explain why an entire second implementation of the same mental model was worth building, what structural debt it paid off, and what the v4 rewrite tells us about where the project — and the pattern — is heading. Everything that follows in this series (Core, Adapters, the Orchestrator, version drift, migration) is downstream of the argument in this article, so it's worth getting precise about it before we go deep on any one subsystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compromise nobody talked about
&lt;/h2&gt;

&lt;p&gt;Webpack Module Federation is, technically, remarkable. It solved cross-application dynamic linking inside a tool that was never designed for it, wrapping every federated module in a runtime container and negotiation layer that webpack manages on your behalf. It works. Thousands of production systems prove it works.&lt;/p&gt;

&lt;p&gt;But it works &lt;em&gt;inside webpack's universe&lt;/em&gt;. Every remote, every host, every build step in that architecture is mediated by webpack's own module runtime — its own resolution logic, its own chunk-loading machinery, its own container format. That's not a criticism of the implementation; it's an acknowledgment of what problem webpack was actually solving when it introduced Module Federation: how do you retrofit dynamic, runtime remote loading onto a bundler whose entire value proposition is &lt;em&gt;static&lt;/em&gt; dependency analysis at build time? The container pattern is the necessary compromise.&lt;/p&gt;

&lt;p&gt;The compromise has a cost, and if you've operated Module Federation at scale, you've paid it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework lock-in at the tooling layer.&lt;/strong&gt; Your host and every remote are implicitly committed to webpack (or a webpack-compatible bundler) for the life of the federated system, because the runtime contract &lt;em&gt;is&lt;/em&gt; a webpack runtime contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A parallel module system living alongside the real one.&lt;/strong&gt; The browser has a native module system — ES Modules — that has matured enormously in recent years. Module Federation doesn't use it; it builds its own resolution and loading logic on top of webpack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration friction that compounds.&lt;/strong&gt; Moving a host off webpack — to Vite, to esbuild, to whatever ships next — means either dragging Module Federation's runtime along as a dependency of a tool you're trying to leave, or re-architecting your federation layer as part of a bundler migration. Two hard problems, coupled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this made Module Federation the wrong choice when it was the only serious option. It made it a choice with an expiration date tied to how long webpack stayed the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "browser-native" actually buys you
&lt;/h2&gt;

&lt;p&gt;Native Federation's core bet is structural: instead of building a container protocol on top of a bundler's runtime, build directly on &lt;strong&gt;ECMAScript Modules and Import Maps&lt;/strong&gt; — both of which are web standards the browser already implements, with no bundler runtime mediating the load.&lt;/p&gt;

&lt;p&gt;Concretely, that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A remote's exposed module is a genuine ES module, served as a genuine &lt;code&gt;.js&lt;/code&gt; file, importable with a genuine dynamic &lt;code&gt;import()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Shared dependency resolution happens through the browser's &lt;strong&gt;import map&lt;/strong&gt; mechanism — a JSON structure that tells the browser "when code asks for &lt;code&gt;react&lt;/code&gt;, resolve it to &lt;em&gt;this&lt;/em&gt; URL" — rather than through a bundler-authored sharing runtime.&lt;/li&gt;
&lt;li&gt;The build tool's job shrinks to what a build tool should do: produce standards-compliant output and a manifest (&lt;code&gt;remoteEntry.json&lt;/code&gt;) describing what's exposed and what's shared. It stops being responsible for &lt;em&gt;how the browser resolves modules at runtime&lt;/em&gt;, because the browser already knows how to do that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the part that's easy to undersell as "just an implementation detail," but for an architecture team it's the whole point: &lt;strong&gt;the bundler becomes replaceable&lt;/strong&gt;. Native Federation ships a reference esbuild adapter and — most maturely — an Angular adapter that hooks directly into Angular's own esbuild-based Application Builder, but the underlying contract (ESM + import maps + a JSON manifest) doesn't require any of that. Swap the build tool, keep the federation contract. That's not true of container-based Module Federation, and it's the single biggest reason to treat this as an infrastructure decision independent of your current framework and bundler choices — not just an Angular-ecosystem convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more in v4 than it did in v3
&lt;/h2&gt;

&lt;p&gt;Native Federation existed well before v4, largely as a feature of the Angular CLI plugin ecosystem — genuinely useful, but organizationally still shaped like "a clever thing the Angular Architects team built for Angular users." v3 proved the ESM/import-map model worked in production. &lt;/p&gt;

&lt;p&gt;v4 is the rewrite that makes that claim credible, and the evidence isn't a changelog bullet point — it's organizational. The project moved out of the original &lt;code&gt;angular-architects/module-federation-plugin&lt;/code&gt; monorepo entirely and now lives under its own GitHub organization, split into independently versioned repositories: a framework-and-bundler-agnostic &lt;strong&gt;core&lt;/strong&gt;, a dedicated &lt;strong&gt;esbuild adapter&lt;/strong&gt;, a dedicated &lt;strong&gt;Angular adapter&lt;/strong&gt;, and a separately maintained &lt;strong&gt;orchestrator&lt;/strong&gt; for runtime consumption. &lt;/p&gt;

&lt;p&gt;That's not cosmetic restructuring. It's the difference between "a plugin with internal modules" and "a platform with published contracts between its layers." When a build tool, a framework adapter, and a runtime consumer are three separately versioned artifacts instead of three files in the same package, you get to ask a much sharper architectural question about any given piece of the system: &lt;em&gt;what does this layer promise to the layer above it, and what can I swap out without breaking that promise?&lt;/em&gt; We'll answer that question concretely for every layer over the next two articles.&lt;/p&gt;

&lt;p&gt;The other structural signal in v4 worth naming now, because it recurs throughout this series: the project explicitly frames itself as moving toward first-class support for frameworks beyond Angular, and toward runtime consumption from environments that aren't single-page applications at all — plain HTML pages, and server-rendered hosts in different ecosystems, consuming the same manifest contract. Angular remains the most mature adapter today by a wide margin, and this series will go deep on it because that's where the tooling is production-ready — but the &lt;em&gt;ceiling&lt;/em&gt; of the architecture is no longer "an Angular thing." That ceiling matters when you're making a five-year platform bet, not just a next-quarter delivery decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Series Roadmap
&lt;/h2&gt;

&lt;p&gt;This is an 8-part series, and each article builds on the ones before it. Here's the full plan, and where we are right now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Mental Model, Revisited&lt;/strong&gt; — why Native Federation exists and what changed in v4 &lt;em&gt;(this article)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anatomy of the v4 Package Graph&lt;/strong&gt; — Core, Adapters, Runtime, and Orchestrator as independently versioned layers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build-Time, End to End&lt;/strong&gt; — the Core library and its Adapters, and what they actually do to your code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Classic Runtime&lt;/strong&gt; — what it got right, and where it hits its ceiling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Orchestrator&lt;/strong&gt; — semver-aware resolution and persistent caching, under the hood&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Drift and Resolution Strategy&lt;/strong&gt; — the governance problem nobody's build pipeline catches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v3 vs. v4&lt;/strong&gt; — a systematic comparison and a real migration playbook&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference Architecture&lt;/strong&gt; — running Native Federation v4 in a regulated enterprise&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Article 2 is next: it takes the org restructuring covered above seriously and maps the actual package graph, so you know precisely which layer you're touching, and which promises it's making to the layers around it, before we go deep on any single one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next in this series: **Anatomy of the v4 Package Graph&lt;/em&gt;* — mapping Core, Adapters, Classic Runtime, and the Orchestrator as independently versioned layers with explicit contracts between them.*&lt;/p&gt;

</description>
      <category>microfrontends</category>
      <category>nativefederation</category>
      <category>architecture</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Angular Schematics Deep Dive — Part 1: Understanding the Architecture</title>
      <dc:creator>Sakthikumaran Navakumar</dc:creator>
      <pubDate>Mon, 09 Mar 2026 02:53:25 +0000</pubDate>
      <link>https://dev.to/sakthicodes22/angular-schematics-deep-dive-part-1-understanding-the-architecture-46i4</link>
      <guid>https://dev.to/sakthicodes22/angular-schematics-deep-dive-part-1-understanding-the-architecture-46i4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is Part 1 of a 6-part series on Angular Schematics.&lt;/strong&gt; The complete roadmap is listed at the end of this article. Future parts cover custom generators (&lt;code&gt;ng generate&lt;/code&gt;), installation schematics (&lt;code&gt;ng add&lt;/code&gt;), migration schematics (&lt;code&gt;ng update&lt;/code&gt;), testing with Angular DevKit, and advanced patterns including Nx integration.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Every time you run &lt;code&gt;ng generate component&lt;/code&gt;, &lt;code&gt;ng add @angular/material&lt;/code&gt;, or &lt;code&gt;ng update&lt;/code&gt;, something remarkably sophisticated is happening beneath the surface. Angular's CLI isn't simply copying template files — it is executing a structured, transactional, and fully testable code transformation pipeline. That pipeline is powered by &lt;strong&gt;Angular Schematics&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For many Angular developers, schematics remain a black box — something the framework uses internally, something Nx and Angular Material ship with, something you might glance at and walk away from. That's a missed opportunity. Understanding schematics is the difference between a developer who &lt;em&gt;uses&lt;/em&gt; Angular tooling and one who &lt;em&gt;extends&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;In this first article, we pull back the curtain completely. We'll examine the conceptual model, the runtime architecture, the four core abstractions, the virtual file system, and the execution pipeline that every schematic runs through. By the end, you'll have a precise mental model of how schematics work — which is the essential prerequisite for building your own.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What Exactly Is a Schematic?&lt;/li&gt;
&lt;li&gt;How the Angular CLI Uses Schematics&lt;/li&gt;
&lt;li&gt;Architecture Overview — The Four Pillars&lt;/li&gt;
&lt;li&gt;The Schematic Execution Pipeline&lt;/li&gt;
&lt;li&gt;Exploring the Built-in Schematics Collection&lt;/li&gt;
&lt;li&gt;When to Create Custom Schematics&lt;/li&gt;
&lt;li&gt;Summary &amp;amp; What's Next&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What Exactly Is a Schematic?
&lt;/h2&gt;

&lt;p&gt;At its core, a schematic is a &lt;strong&gt;pure function that describes file system transformations&lt;/strong&gt;. It receives a representation of the file system as input and returns a description of how that file system should change — without directly touching disk until all transformations are validated and committed.&lt;/p&gt;

&lt;p&gt;That definition contains three ideas worth unpacking carefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It's a pure function.&lt;/strong&gt; Schematics follow functional programming principles. A schematic doesn't mutate the world directly. It receives state, produces a new description of state, and hands that description off to a runner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It describes transformations — it doesn't execute them.&lt;/strong&gt; Creating a file in a schematic isn't the same as calling &lt;code&gt;fs.writeFileSync()&lt;/code&gt;. You're building a description of what should happen, giving the framework an opportunity to validate, preview, and batch everything before committing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It operates on a virtual file system.&lt;/strong&gt; The file system schematics work with is an in-memory representation. This is what makes dry-run mode, transactional rollbacks, and unit testing possible and practical.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;A useful mental model:&lt;/strong&gt; Think of a schematic the way a database thinks of a transaction. You open a transaction (the virtual Tree), describe all your changes (Rules), and then either commit them all atomically or roll back entirely. No partial states, no corrupted workspaces.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How the Angular CLI Uses Schematics
&lt;/h2&gt;

&lt;p&gt;Angular CLI commands map directly to schematics. When you run any of the following, you are invoking a schematic from a registered collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Invokes the 'component' schematic from @schematics/angular&lt;/span&gt;
ng generate component my-feature

&lt;span class="c"&gt;# Invokes the 'ng-add' schematic from @angular/material's package.json&lt;/span&gt;
ng add @angular/material

&lt;span class="c"&gt;# Invokes migration schematics listed in the package's migrations.json&lt;/span&gt;
ng update @angular/core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mapping is not magic — it's configuration. Every Angular library that participates in this ecosystem declares its schematics in &lt;code&gt;package.json&lt;/code&gt; through two fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Example Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"schematics"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to collection.json for ng generate / ng add&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"./schematics/collection.json"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"ng-update"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Points to migrations.json for ng update&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"migrations": "./migrations.json"}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;collection.json&lt;/code&gt; acts as a manifest — a registry of all named schematics the package exposes, the TypeScript factory function for each, and the JSON schema that defines the schematic's options. The CLI reads this manifest, resolves the factory, and hands it to the DevKit runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Overview — The Four Pillars
&lt;/h2&gt;

&lt;p&gt;The Angular DevKit (&lt;code&gt;@angular-devkit/schematics&lt;/code&gt;) is built around four foundational abstractions. Understanding each one precisely is the prerequisite for writing effective schematics.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Tree — The Virtual File System
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Tree&lt;/code&gt; is the representation of the workspace as the schematic sees it. It is an immutable snapshot combined with a &lt;strong&gt;staged layer of mutations&lt;/strong&gt; that haven't been applied yet. When you call &lt;code&gt;tree.create()&lt;/code&gt; or &lt;code&gt;tree.overwrite()&lt;/code&gt;, you are adding entries to that staging layer — not touching disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-devkit/schematics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mySchematic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MyOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// READ — returns buffer or null (no I/O hits disk)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;angular.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// WRITE — stages a change, does NOT write to disk&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/my-file.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;export const x = 42;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// OVERWRITE — also staged, not committed yet&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;overwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/app/app.module.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// return the mutated tree&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Tree also exposes &lt;code&gt;tree.exists()&lt;/code&gt;, &lt;code&gt;tree.delete()&lt;/code&gt;, &lt;code&gt;tree.rename()&lt;/code&gt;, and &lt;code&gt;tree.getDir()&lt;/code&gt; for directory traversal. Everything is synchronous and backed by in-memory buffers unless you are reading a file that only exists on disk and hasn't been modified yet.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Rule — The Unit of Work
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;Rule&lt;/code&gt; is the atomic unit of transformation in schematics. Its type signature is elegantly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From @angular-devkit/schematics internals&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules are composable. The DevKit provides &lt;code&gt;chain()&lt;/code&gt; to compose multiple rules sequentially, &lt;code&gt;branchAndMerge()&lt;/code&gt; to run rules on a branched copy of the Tree, and &lt;code&gt;mergeWith()&lt;/code&gt; to merge a generated file source into the existing Tree. This composability is what makes schematics architecturally powerful — complex operations are built from simple, independently testable pieces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mergeWith&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-devkit/schematics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;myFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;addFilesFromTemplates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// Rule 1&lt;/span&gt;
    &lt;span class="nf"&gt;updateAngularJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;      &lt;span class="c1"&gt;// Rule 2&lt;/span&gt;
    &lt;span class="nf"&gt;addImportToAppModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// Rule 3&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. Source — Generating a New Tree
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;Source&lt;/code&gt; is a factory that produces an entirely new, empty Tree from some origin — typically a directory of template files. The most commonly used Source is &lt;code&gt;url()&lt;/code&gt;, which reads template files from a local directory. Sources are merged into the main Tree using &lt;code&gt;mergeWith()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;applyTemplates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mergeWith&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-devkit/schematics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;strings&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-devkit/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;templateSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;applyTemplates&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dasherize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dasherize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mergeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateSource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. SchematicContext — Runtime Information
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;SchematicContext&lt;/code&gt; is passed alongside the Tree into every Rule. It provides access to the logger (use this instead of &lt;code&gt;console.log&lt;/code&gt;), task scheduling (for running npm install after file generation), and the engine executing the schematic. It's the schematic's connection to the outside world.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NodePackageInstallTask&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular-devkit/schematics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Installing dependencies...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Schedule npm install to run AFTER all file mutations commit&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodePackageInstallTask&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Schematic Execution Pipeline
&lt;/h2&gt;

&lt;p&gt;With the four pillars understood, let's trace the full lifecycle of a schematic execution from CLI command to committed files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLI Command
───────────
ng generate component dashboard

     │
     ▼
CLI resolves collection
─────────────────────────
Reads package.json → "schematics": "./schematics/collection.json"
Reads collection.json → finds "component" entry → factory path

     │
     ▼
JSON Schema Validation
──────────────────────
Merges CLI flags + prompts against schema.json
Validates required fields, applies defaults
Prompts user for missing required options

     │
     ▼
Factory Function Invoked
─────────────────────────
schematic(options) → returns root Rule

     │
     ▼
Engine Creates Tree
────────────────────
Base layer  = current workspace on disk
Staging layer = empty (no mutations yet)

     │
     ▼
Rule Chain Executes
────────────────────
Rule 1: Generate files from templates → staged
Rule 2: Update angular.json            → staged
Rule 3: Add import to app.module.ts    → staged

     │
     ▼
Post-Rule Tasks Run
────────────────────
NodePackageInstallTask (if scheduled)
RunSchematicTask (chained schematics)

     │
     ▼
Commit (unless --dry-run)
──────────────────────────
Staging layer flushed to real disk
Console output: CREATE / UPDATE / DELETE logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Exploring the Built-in Schematics Collection
&lt;/h2&gt;

&lt;p&gt;The best way to learn schematic patterns is to read production-grade schematics. The entire &lt;code&gt;@schematics/angular&lt;/code&gt; package is open source and worth studying carefully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install to inspect locally&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @schematics/angular &lt;span class="nt"&gt;--save-dev&lt;/span&gt;

&lt;span class="c"&gt;# Browse on GitHub:&lt;/span&gt;
&lt;span class="c"&gt;# github.com/angular/angular-cli/tree/main/packages/schematics/angular&lt;/span&gt;

&lt;span class="c"&gt;# List all available schematics in the built-in collection&lt;/span&gt;
ng generate &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside &lt;code&gt;@schematics/angular&lt;/code&gt; you'll find the implementation for every built-in generator: &lt;code&gt;component&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;module&lt;/code&gt;, &lt;code&gt;guard&lt;/code&gt;, &lt;code&gt;pipe&lt;/code&gt;, &lt;code&gt;directive&lt;/code&gt;, &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;enum&lt;/code&gt;, &lt;code&gt;interface&lt;/code&gt;, and &lt;code&gt;application&lt;/code&gt;. Each one follows the same pattern: a &lt;code&gt;schema.json&lt;/code&gt; defining options, an &lt;code&gt;index.ts&lt;/code&gt; factory, and a &lt;code&gt;files/&lt;/code&gt; directory of templates.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Schematic&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Interesting Technique&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;component&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/component/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Conditional template rendering based on &lt;code&gt;--standalone&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/module/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Routing module generation, lazy-load wiring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;guard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/guard/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Multiple implementations based on &lt;code&gt;--implements&lt;/code&gt; flag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;application&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/application/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Complete workspace scaffolding, cross-schematic calls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Practical Tip:&lt;/strong&gt; When learning schematics, clone the Angular CLI repo and read the &lt;code&gt;component&lt;/code&gt; schematic in its entirety. It demonstrates conditional template inclusion, AST-based module updates, path normalization, and option defaults — all the patterns you'll use in production schematics.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  When to Create Custom Schematics
&lt;/h2&gt;

&lt;p&gt;Having established the technical foundation, the natural question is: when does writing a custom schematic become the right investment?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeated scaffolding patterns.&lt;/strong&gt; If your team frequently creates a "feature module" that always includes a component, a service, a store, and a routing module — and that setup consistently takes 15 minutes of copy-pasting — you have a schematic waiting to be written. A single &lt;code&gt;ng generate feature dashboard&lt;/code&gt; should handle it in under a second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enforcing architectural rules.&lt;/strong&gt; Custom schematics are a powerful mechanism for ensuring generated code follows your team's conventions. Folder structures, naming patterns, required barrel files, mandatory test setups — all of these can be encoded into a schematic rather than documented in a wiki that no one reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Library installation complexity.&lt;/strong&gt; If your internal UI library requires updating &lt;code&gt;angular.json&lt;/code&gt;, modifying global styles, adding a provider to &lt;code&gt;app.config.ts&lt;/code&gt;, and installing two peer dependencies — an &lt;code&gt;ng add&lt;/code&gt; schematic automates all of that into a single, repeatable, idempotent operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework migrations.&lt;/strong&gt; When you introduce a breaking change in your shared library — a renamed method, a restructured API, a changed import path — an &lt;code&gt;ng update&lt;/code&gt; schematic can apply that migration automatically across every workspace that consumes your library. This is how Angular itself ships breaking changes responsibly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🚦 &lt;strong&gt;The Decision Test:&lt;/strong&gt; Ask yourself: &lt;em&gt;"Would I describe this task in an onboarding doc?"&lt;/em&gt; If yes, the task is probably worth automating with a schematic. Documentation describes what humans should do manually. Schematics eliminate the need for the documentation entirely.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Summary &amp;amp; What's Next
&lt;/h2&gt;

&lt;p&gt;We've covered significant ground. Here's what to take forward:&lt;/p&gt;

&lt;p&gt;A schematic is a &lt;strong&gt;pure function that describes file system transformations&lt;/strong&gt; on a virtual, in-memory representation of the workspace. The Angular CLI maps its core commands directly to schematics declared in package manifests. The DevKit runtime is built on four core abstractions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tree&lt;/strong&gt; — virtual file system with a staging layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule&lt;/strong&gt; — atomic, composable unit of transformation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt; — template file origin that produces a new Tree&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SchematicContext&lt;/strong&gt; — runtime connection for logging and task scheduling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The virtual file system is the design decision that enables everything valuable about schematics: dry-run previews, atomic rollback, and millisecond-fast unit testing. The execution pipeline runs schema validation, factory invocation, rule chain execution, task scheduling, and a final atomic commit.&lt;/p&gt;

&lt;p&gt;Understanding this architecture deeply is not just academic. It is the precise foundation upon which every practical schematic is built. In &lt;strong&gt;Part 2&lt;/strong&gt;, we take this knowledge directly into the editor and build our first real custom generator — a feature module scaffolder that produces a complete, production-ready module structure in a single command.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series Roadmap
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Understanding Angular Schematics — Architecture &amp;amp; Core Concepts&lt;/td&gt;
&lt;td&gt;✅ You are here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 2&lt;/td&gt;
&lt;td&gt;Creating Custom Generators with &lt;code&gt;ng generate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;🔜 Coming Soon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 3&lt;/td&gt;
&lt;td&gt;Building Installation Schematics with &lt;code&gt;ng add&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;🔜 Coming Soon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 4&lt;/td&gt;
&lt;td&gt;Writing Migration Schematics with &lt;code&gt;ng update&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;🔜 Coming Soon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 5&lt;/td&gt;
&lt;td&gt;Testing Schematics with Angular DevKit&lt;/td&gt;
&lt;td&gt;🔜 Coming Soon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 6&lt;/td&gt;
&lt;td&gt;Advanced Patterns, Publishing &amp;amp; Monorepo Integration&lt;/td&gt;
&lt;td&gt;🔜 Coming Soon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Previous Blogs:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/sakthicodes22/the-nx-cheatsheet-commands-for-daily-development-3h4k"&gt;https://dev.to/sakthicodes22/the-nx-cheatsheet-commands-for-daily-development-3h4k&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev.to/sakthicodes22/stop-the-spaghetti-enforcing-module-boundaries-in-an-nx-monorepo-2a24"&gt;https://dev.to/sakthicodes22/stop-the-spaghetti-enforcing-module-boundaries-in-an-nx-monorepo-2a24&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Found this useful? Drop a ❤️ and share it with a teammate who would be intersted.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>schematics</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Nx Cheatsheet — Commands for Daily Development</title>
      <dc:creator>Sakthikumaran Navakumar</dc:creator>
      <pubDate>Mon, 02 Mar 2026 15:17:27 +0000</pubDate>
      <link>https://dev.to/sakthicodes22/the-nx-cheatsheet-commands-for-daily-development-3h4k</link>
      <guid>https://dev.to/sakthicodes22/the-nx-cheatsheet-commands-for-daily-development-3h4k</guid>
      <description>&lt;p&gt;If you have just started working with &lt;strong&gt;Nx&lt;/strong&gt; — or joined a team that uses it — the CLI can feel overwhelming at first. There are dozens of commands, flags, and concepts to absorb all at once.&lt;/p&gt;

&lt;p&gt;This cheatsheet cuts through the noise. It covers the &lt;strong&gt;commands you will actually use every day&lt;/strong&gt;, organised by workflow, explained plainly. Bookmark it, come back to it, share it with your team. Complete List of CLI commands can be found &lt;a href="https://nx.dev/docs/reference/nx-commands" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What is Nx?&lt;/li&gt;
&lt;li&gt;Creating a Workspace&lt;/li&gt;
&lt;li&gt;Adding Plugins&lt;/li&gt;
&lt;li&gt;Generating Projects and Code&lt;/li&gt;
&lt;li&gt;Running and Serving Applications&lt;/li&gt;
&lt;li&gt;Running Multiple Tasks&lt;/li&gt;
&lt;li&gt;Filtering Projects with Patterns and Tags&lt;/li&gt;
&lt;li&gt;Building Projects&lt;/li&gt;
&lt;li&gt;The Affected Commands&lt;/li&gt;
&lt;li&gt;Skipping and Managing the Cache&lt;/li&gt;
&lt;li&gt;Running Tests&lt;/li&gt;
&lt;li&gt;Linting and Formatting&lt;/li&gt;
&lt;li&gt;Visualising the Dependency Graph&lt;/li&gt;
&lt;li&gt;Workspace and Project Info&lt;/li&gt;
&lt;li&gt;The Nx Daemon&lt;/li&gt;
&lt;li&gt;Quick Reference Cheat Sheet&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What is Nx?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nx&lt;/strong&gt; is an open-source build system and monorepo toolkit. It lets you manage all your applications and shared libraries in a single repository while giving you intelligent tooling to build, test, and serve them efficiently.&lt;/p&gt;

&lt;p&gt;Three things make Nx stand out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;strong&gt;Smart task runner&lt;/strong&gt; — understands your project dependency graph and only rebuilds what has actually changed&lt;/li&gt;
&lt;li&gt;🏗️ &lt;strong&gt;Code generation&lt;/strong&gt; — scaffolds apps, libraries, and components using best-practice templates&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Visualisation&lt;/strong&gt; — interactive graph to see how all your projects connect&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Creating a Workspace
&lt;/h2&gt;

&lt;p&gt;Everything starts here. A workspace is the root container for all your applications and libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a new workspace interactively&lt;/span&gt;
npx create-nx-workspace@latest

&lt;span class="c"&gt;# Create with a specific framework preset&lt;/span&gt;
&lt;span class="c"&gt;# (react, angular, next, node, ts, empty)&lt;/span&gt;
npx create-nx-workspace@latest &lt;span class="nt"&gt;--preset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;react

&lt;span class="c"&gt;# Create a blank monorepo with a custom name&lt;/span&gt;
npx create-nx-workspace@latest &lt;span class="nt"&gt;--preset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;empty &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myorg

&lt;span class="c"&gt;# Specify your preferred package manager (npm, yarn, pnpm)&lt;/span&gt;
npx create-nx-workspace@latest &lt;span class="nt"&gt;--packageManager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pnpm

&lt;span class="c"&gt;# Add Nx to an already-existing project or monorepo&lt;/span&gt;
nx init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Adding Plugins
&lt;/h2&gt;

&lt;p&gt;Nx is plugin-driven. Plugins provide the framework-specific generators and executors for React, Angular, Node, and more. The &lt;code&gt;nx add&lt;/code&gt; command installs a plugin &lt;strong&gt;and&lt;/strong&gt; automatically runs its initialisation generator in one step — no manual wiring required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the React plugin and auto-initialize it&lt;/span&gt;
nx add @nx/react

&lt;span class="c"&gt;# Install the Angular plugin and wire it up automatically&lt;/span&gt;
nx add @nx/angular
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Generating Projects and Code
&lt;/h2&gt;

&lt;p&gt;Instead of manually creating folders and configuration files, let Nx do it for you. The &lt;code&gt;nx generate&lt;/code&gt; (or &lt;code&gt;nx g&lt;/code&gt;) command creates everything following best practices for your chosen framework.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate a new application&lt;/span&gt;
nx g app &amp;lt;n&amp;gt;

&lt;span class="c"&gt;# Generate a new shared library&lt;/span&gt;
nx g lib &amp;lt;n&amp;gt;

&lt;span class="c"&gt;# Generate a component (framework-specific)&lt;/span&gt;
nx g component &amp;lt;n&amp;gt;

&lt;span class="c"&gt;# Preview what will be generated WITHOUT writing any files&lt;/span&gt;
nx g app &amp;lt;n&amp;gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Running and Serving Applications
&lt;/h2&gt;

&lt;p&gt;Nx provides a consistent interface for running your projects regardless of which framework or tooling is underneath. &lt;code&gt;nx serve myapp&lt;/code&gt; is shorthand for &lt;code&gt;nx run myapp:serve&lt;/code&gt; — both do the same thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start a local development server&lt;/span&gt;
nx serve &amp;lt;project&amp;gt;

&lt;span class="c"&gt;# Run any defined target on a project&lt;/span&gt;
nx run &amp;lt;project&amp;gt;:&amp;lt;target&amp;gt;

&lt;span class="c"&gt;# Run a target with a specific configuration (e.g. production, staging)&lt;/span&gt;
nx run &amp;lt;project&amp;gt;:&amp;lt;target&amp;gt;:&amp;lt;configuration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Running Multiple Tasks
&lt;/h2&gt;

&lt;p&gt;As your monorepo grows, you need to run operations across multiple projects at once. &lt;code&gt;nx run-many&lt;/code&gt; fans out work across your workspace in parallel. The default parallelism is &lt;code&gt;3&lt;/code&gt; — increase it based on your machine's capacity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run multiple targets across ALL projects in one command&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; build &lt;span class="nb"&gt;test &lt;/span&gt;lint

&lt;span class="c"&gt;# Run multiple targets on specific named projects only&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; build &lt;span class="nb"&gt;test &lt;/span&gt;lint &lt;span class="nt"&gt;-p&lt;/span&gt; app1 app2

&lt;span class="c"&gt;# Run tasks with a concurrency limit&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; build &lt;span class="nt"&gt;--parallel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5

&lt;span class="c"&gt;# Run with the interactive Terminal UI (new in Nx v22)&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; build &lt;span class="nt"&gt;--outputStyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Filtering Projects with Patterns and Tags
&lt;/h2&gt;

&lt;p&gt;Instead of listing every project name manually, use glob patterns and Nx project tags to target exactly the right set of projects. Tags are labels you assign to projects in their config — for example &lt;code&gt;scope:frontend&lt;/code&gt; or &lt;code&gt;type:ui&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run only projects tagged with scope:frontend&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; build &lt;span class="nt"&gt;--projects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'tag:scope:frontend'&lt;/span&gt;

&lt;span class="c"&gt;# Run only projects whose names end with -app&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; build &lt;span class="nt"&gt;--projects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*-app'&lt;/span&gt;

&lt;span class="c"&gt;# Mix explicit names and glob patterns&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--projects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'app1,app2,shared-*'&lt;/span&gt;

&lt;span class="c"&gt;# Run on all projects, EXCLUDING e2e projects&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; lint &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*-e2e'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Building Projects
&lt;/h2&gt;

&lt;p&gt;Building compiles your source code and produces deployment-ready artifacts. The command is the same regardless of whether you use Webpack, Vite, or esbuild underneath.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build a specific project&lt;/span&gt;
nx build &amp;lt;project&amp;gt;

&lt;span class="c"&gt;# Build using the production configuration (optimised, minified)&lt;/span&gt;
nx build &amp;lt;project&amp;gt; &lt;span class="nt"&gt;--configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="c"&gt;# Build all projects in the workspace&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. The Affected Commands — Nx's Superpower
&lt;/h2&gt;

&lt;p&gt;This is the feature that makes Nx genuinely transformative. Instead of rebuilding and retesting everything on every commit, Nx analyses your dependency graph and figures out &lt;strong&gt;exactly which projects are affected by your changes&lt;/strong&gt; — and only runs tasks for those.&lt;/p&gt;

&lt;p&gt;If you change a utility library, Nx identifies every app and library that depends on it and runs the target for all of them. Everything else is skipped. On a large monorepo, this can reduce CI time significantly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build only projects affected by recent changes&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; build

&lt;span class="c"&gt;# Test only affected projects&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# Lint only affected projects&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; lint

&lt;span class="c"&gt;# Run multiple targets on affected projects in one command&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; build &lt;span class="nb"&gt;test &lt;/span&gt;lint

&lt;span class="c"&gt;# Determine affected projects relative to a specific branch/commit range&lt;/span&gt;
nx affected &lt;span class="nt"&gt;--base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="nt"&gt;--head&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Skipping and Managing the Cache
&lt;/h2&gt;

&lt;p&gt;Nx caches the results of every task. If nothing has changed since the last run, Nx replays the cached result in milliseconds. There are times, however, when you need to bypass the cache — debugging a flaky test, verifying a fix actually works, or clearing stale output.&lt;/p&gt;

&lt;p&gt;Refer here for &lt;a href="https://nx.dev/docs/concepts/how-caching-works" rel="noopener noreferrer"&gt;How caching works&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run a build bypassing the local cache entirely&lt;/span&gt;
nx build &amp;lt;project&amp;gt; &lt;span class="nt"&gt;--skipNxCache&lt;/span&gt;

&lt;span class="c"&gt;# Skip cache for all projects in run-many&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; build &lt;span class="nt"&gt;--skipNxCache&lt;/span&gt;

&lt;span class="c"&gt;# Skip cache on all affected test runs&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--skipNxCache&lt;/span&gt;

&lt;span class="c"&gt;# Clear the entire local cache AND daemon state&lt;/span&gt;
nx reset

&lt;span class="c"&gt;# Clear ONLY the task cache, leave the daemon running&lt;/span&gt;
nx reset &lt;span class="nt"&gt;--only-cache&lt;/span&gt;

&lt;span class="c"&gt;# Restart ONLY the daemon, leave the cache intact&lt;/span&gt;
nx reset &lt;span class="nt"&gt;--only-daemon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Running Tests
&lt;/h2&gt;

&lt;p&gt;Whether your projects use Jest, Vitest, Playwright, or Cypress, &lt;code&gt;nx test&lt;/code&gt; and &lt;code&gt;nx e2e&lt;/code&gt; provide a consistent interface. Combine with &lt;code&gt;affected&lt;/code&gt; and &lt;code&gt;run-many&lt;/code&gt; for full-scale coverage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run unit tests for a specific project&lt;/span&gt;
nx &lt;span class="nb"&gt;test&lt;/span&gt; &amp;lt;project&amp;gt;

&lt;span class="c"&gt;# Run end-to-end tests for a project&lt;/span&gt;
nx e2e &amp;lt;project&amp;gt;

&lt;span class="c"&gt;# Run unit tests only for affected projects&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# Run unit tests across ALL projects&lt;/span&gt;
nx run-many &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  11. Linting and Formatting
&lt;/h2&gt;

&lt;p&gt;Keeping code quality consistent across a monorepo is hard without tooling. Nx gives every project its own lint target and provides workspace-level format commands powered by Prettier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Lint a specific project&lt;/span&gt;
nx lint &amp;lt;project&amp;gt;

&lt;span class="c"&gt;# Lint only the projects affected by recent changes&lt;/span&gt;
nx affected &lt;span class="nt"&gt;-t&lt;/span&gt; lint

&lt;span class="c"&gt;# Auto-format ALL files in the workspace using Prettier&lt;/span&gt;
nx format:write

&lt;span class="c"&gt;# Check formatting across the workspace WITHOUT writing (perfect for CI)&lt;/span&gt;
nx format:check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  12. Visualising the Dependency Graph
&lt;/h2&gt;

&lt;p&gt;At any point you can visualise the entire architecture of your monorepo as an interactive browser-based graph. This is invaluable for onboarding new team members, auditing architecture, and understanding the blast radius of a change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Open the interactive dependency graph in the browser&lt;/span&gt;
nx graph

&lt;span class="c"&gt;# Show only the subgraph of a specific project and its dependencies&lt;/span&gt;
nx graph &lt;span class="nt"&gt;--focus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;project&amp;gt;

&lt;span class="c"&gt;# Print the project graph as JSON to the terminal&lt;/span&gt;
nx graph &lt;span class="nt"&gt;--print&lt;/span&gt;

&lt;span class="c"&gt;# Export the full graph to a JSON file&lt;/span&gt;
nx graph &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;output.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  13. Workspace and Project Info
&lt;/h2&gt;

&lt;p&gt;As your workspace grows, you need reliable ways to explore it. The &lt;code&gt;nx show&lt;/code&gt;, &lt;code&gt;nx list&lt;/code&gt;, and &lt;code&gt;nx report&lt;/code&gt; commands answer the everyday questions: what exists, what is affected, what plugins are installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List ALL projects in the workspace&lt;/span&gt;
nx show projects

&lt;span class="c"&gt;# List only the projects affected by recent changes&lt;/span&gt;
&lt;span class="c"&gt;# (modern replacement for the deprecated nx affected:apps/libs)&lt;/span&gt;
nx show projects &lt;span class="nt"&gt;--affected&lt;/span&gt;

&lt;span class="c"&gt;# List all projects that have a specific target defined&lt;/span&gt;
nx show projects &lt;span class="nt"&gt;--with-target&lt;/span&gt; serve

&lt;span class="c"&gt;# Show full configuration details of a specific project&lt;/span&gt;
nx show project &amp;lt;n&amp;gt;

&lt;span class="c"&gt;# Open a rich browser view of a project's fully resolved configuration&lt;/span&gt;
nx show project &amp;lt;n&amp;gt; &lt;span class="nt"&gt;--web&lt;/span&gt;

&lt;span class="c"&gt;# List all installed Nx plugins in the workspace&lt;/span&gt;
nx list

&lt;span class="c"&gt;# List all generators and executors provided by a specific plugin&lt;/span&gt;
nx list &amp;lt;plugin&amp;gt;

&lt;span class="c"&gt;# Print all installed Nx and plugin versions (essential for debugging)&lt;/span&gt;
nx report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  14. The Nx Daemon
&lt;/h2&gt;

&lt;p&gt;The Nx Daemon is a background process that keeps your workspace's project graph in memory. Without it, every command has to re-read and re-parse your entire workspace from scratch — noticeable latency in large monorepos. In most cases it starts and stops automatically. These commands give you manual control when needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Manually start the Nx daemon&lt;/span&gt;
nx daemon &lt;span class="nt"&gt;--start&lt;/span&gt;

&lt;span class="c"&gt;# Stop the Nx daemon&lt;/span&gt;
nx daemon &lt;span class="nt"&gt;--stop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚡ Quick Reference Cheat Sheet
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🏗️ Workspace Setup
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npx create-nx-workspace@latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a new workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add Nx to an existing project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx add @nx/react&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add and initialize a plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🔧 Generate Code
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx g app &amp;lt;n&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx g lib &amp;lt;n&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New shared library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx g component &amp;lt;n&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx g app &amp;lt;n&amp;gt; --dry-run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preview generation without writing files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  ▶️ Run Tasks
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx serve &amp;lt;project&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start dev server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx build &amp;lt;project&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build a project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx test &amp;lt;project&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run unit tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx e2e &amp;lt;project&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run e2e tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx lint &amp;lt;project&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lint a project&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🚀 Scale Across Projects
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx run-many -t build test lint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run multiple targets across all projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx run-many -t build --parallel=5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run with concurrency control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx run-many -t build --projects='*-app'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target projects by glob pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx run-many -t build --projects='tag:scope:frontend'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target projects by tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx run-many -t lint --exclude='*-e2e'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exclude projects by pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx affected -t build test lint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only run what is affected by your changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx affected --base=main --head=HEAD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Affected in a PR commit range&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🗄️ Cache Management
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx build &amp;lt;project&amp;gt; --skipNxCache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bypass cache for one task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx reset --only-cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clear task cache only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx reset --only-daemon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restart daemon only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full reset — cache + daemon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🔍 Explore &amp;amp; Debug
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx graph&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open interactive dependency graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx graph --focus=&amp;lt;project&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Focus graph on one project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx show projects --affected&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List affected projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx show project &amp;lt;n&amp;gt; --web&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inspect full project config in browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx report&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check all installed plugin versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx format:check&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check formatting (great for CI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nx format:write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto-format the entire workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Useful Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📖 &lt;a href="https://nx.dev/docs" rel="noopener noreferrer"&gt;Official Nx Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔧 &lt;a href="https://nx.dev/docs/reference/nx-commands" rel="noopener noreferrer"&gt;Nx CLI Reference (v22)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Found this useful? Drop a ❤️ and share it with a teammate who is just getting started with Nx.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nx</category>
      <category>monorepo</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop the Spaghetti: Enforcing Module Boundaries in an Nx Monorepo</title>
      <dc:creator>Sakthikumaran Navakumar</dc:creator>
      <pubDate>Sat, 28 Feb 2026 14:44:59 +0000</pubDate>
      <link>https://dev.to/sakthicodes22/stop-the-spaghetti-enforcing-module-boundaries-in-an-nx-monorepo-2a24</link>
      <guid>https://dev.to/sakthicodes22/stop-the-spaghetti-enforcing-module-boundaries-in-an-nx-monorepo-2a24</guid>
      <description>&lt;h2&gt;
  
  
  Enforcing Module Boundaries in an Nx Monorepo
&lt;/h2&gt;

&lt;p&gt;It starts with good intentions. Your team decides to adopt a monorepo. You scaffold your workspace, create a handful of libraries, and for the first few sprints everything feels clean and purposeful. Then the deadlines hit.&lt;/p&gt;

&lt;p&gt;A developer needs a formatting utility from the payments module — so they reach in and import it directly. Another engineer needs a component from the loans feature for a quick prototype in the accounts section. Someone else, pressed for time, imports a service three layers deep from another domain's internals. Nobody reviews it too carefully. The CI pipeline is green. Ship it.&lt;/p&gt;

&lt;p&gt;Six months later, your dependency graph looks like a bowl of spaghetti. The payments domain knows about accounts. The accounts feature imports from loans. Shared utilities carry domain-specific logic. Nobody can confidently change anything without triggering a cascade of broken imports across the workspace. Your architecture diagram, proudly mounted on the team's Confluence page, bears no resemblance to what the codebase actually does.&lt;br&gt;
This is not a discipline problem. It is a tooling problem. And Nx solves it elegantly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Module Boundaries Matter: The Architectural Case
&lt;/h2&gt;

&lt;p&gt;Before we look at the tooling, it is worth understanding why boundary enforcement is not just a nice-to-have but an architectural necessity in any sufficiently large codebase.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implicit Coupling Is the Silent Killer
&lt;/h2&gt;

&lt;p&gt;In a monorepo without enforced boundaries, any library can import from any other library. Technically, there is nothing stopping a UI component from reaching directly into a data-access service, or a feature module from importing internal implementation details from a completely unrelated domain. These imports create implicit coupling — undocumented, invisible dependencies that accumulate quietly until refactoring becomes genuinely dangerous.&lt;/p&gt;
&lt;h2&gt;
  
  
  Social Enforcement Does Not Scale
&lt;/h2&gt;

&lt;p&gt;With a team of three engineers and ten libraries, you can maintain architectural discipline through code review and shared understanding. With a team of fifteen engineers, forty libraries, and three concurrent feature tracks, you cannot. The cognitive overhead of manually auditing import paths in code review is enormous, the feedback loop is slow, and violations slip through. You need the tooling to carry the architectural intent forward, independent of team size, experience level, or deadline pressure.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Architecture Diagram Should Match the Import Graph
&lt;/h2&gt;

&lt;p&gt;This is the principle that underpins everything. If your architecture diagram shows that the payments domain is isolated from the loans domain, then your import graph should reflect exactly that. Nx module boundary enforcement is the mechanism that keeps these two things in sync — automatically, continuously, and without relying on human vigilance.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Nx Mental Model: Tags and Dependency Constraints
&lt;/h2&gt;

&lt;p&gt;Nx enforces boundaries through a tag-based system. Each library in your workspace is assigned one or more tags via its project.json file, and your ESLint configuration defines rules that govern which tags are allowed to depend on which other tags.&lt;/p&gt;

&lt;p&gt;Tags typically carry two dimensions of information: scope and type.&lt;/p&gt;

&lt;p&gt;Scope answers the question: which domain or vertical does this library belong to? In a banking application, you might have scopes like scope:payments, scope:loans, scope:accounts, scope:kyc (Know Your Customer), and scope:shared.&lt;/p&gt;

&lt;p&gt;Type answers the question: what architectural layer or role does this library play? Rather than the generic feature/ui/data-access/util convention, a more expressive and flexible approach uses tags like type:app, type:lib, type:shared, and type:e2e. This maps more naturally to how libraries actually behave in large-scale production workspaces and gives you coarser, more durable rules.&lt;/p&gt;

&lt;p&gt;Here is how tags are applied in a library's project.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//apps/banking-portal-e&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;e/project.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"banking-portal-e2e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"projectType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"scope:banking-portal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type:e2e"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your libraries are tagged, the ESLint rule &lt;code&gt;@nx/enforce-module-boundaries&lt;/code&gt; becomes the enforcement engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Sample Project Structure
&lt;/h2&gt;

&lt;p&gt;Before diving into the ESLint configuration, here is the workspace structure we will be working with throughout this article. This represents a simplified but realistic banking platform monorepo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;banking-workspace/
├── apps/
│   ├── banking-portal/       # Customer-facing web app
│   │   └── project.json      # tags: scope:banking-portal, type:app
│   ├── banking-portal-e2e/   # E2E tests for banking-portal
│   │   └── project.json      # tags: scope:banking-portal, type:e2e
│   ├── admin-dashboard/      # Internal ops/admin app
│       └── project.json      # tags: scope:admin, type:app  
├── libs/
│   ├── payments/
│   │   ├── feature-transfer/ # tags: scope:payments, type:lib
│   │   ├── feature-transaction-history/ # tags: scope:payments, type:lib
│   │   └── data-access/      # tags: scope:payments, type:lib
│   ├── loans/
│   │   ├── feature-apply/    # tags: scope:loans, type:lib
│   │   ├── feature-repayment/# tags: scope:loans, type:lib
│   │   └── data-access/      # tags: scope:loans, type:lib
│   ├── accounts/
│   │   ├── feature-dashboard/# tags: scope:accounts, type:lib
│   │   ├── feature-settings/ # tags: scope:accounts, type:lib
│   │   └── data-access/      # tags: scope:accounts, type:lib
│   ├── kyc/
│   │   ├── feature-onboarding/# tags: scope:kyc, type:lib
│   │   └── data-access/       # tags: scope:kyc, type:lib
│   └── shared/
│       ├── ui-design-system/  # tags: scope:shared, type:shared
│       ├── ui-forms/          # tags: scope:shared, type:shared
│       ├── util-formatters/   # tags: scope:shared, type:shared
│       ├── util-validators/   # tags: scope:shared, type:shared
│       └── data-access-http/  # tags: scope:shared, type:shared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every domain (payments, loans, accounts, kyc) is a self-contained vertical. The shared scope holds anything that is genuinely cross-cutting. Apps consume libs. Libs do not reach into other domain's libs unless explicitly permitted. E2E projects only test — they never become a source of shared logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcing Boundaries with the Nx ESLint Plugin
&lt;/h2&gt;

&lt;p&gt;With the project structure established, let us look at the ESLint configuration that encodes these architectural rules. All of this lives in your root .eslintrc.json.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@nx"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.tsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.jsx"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@nx/enforce-module-boundaries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"enforceBuildableLibDependency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"allowCircularSelfDependency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"banTransitiveDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"depConstraints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;

              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type:app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type:lib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type:lib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type:lib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type:shared"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type:e2e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"scope:payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:loans"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"scope:loans"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:accounts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"scope:accounts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:kyc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"scope:kyc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scope:shared"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"scope:shared"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  This configuration encodes six architectural decisions simultaneously:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Apps can consume domain libs and shared libs — but never other apps&lt;/li&gt;
&lt;li&gt;Domain libs can consume other libs within their own scope and anything in shared — but never reach across domain boundaries&lt;/li&gt;
&lt;li&gt;Shared libs are the foundation layer — they are self-contained and import nothing outside their own scope&lt;/li&gt;
&lt;li&gt;E2E projects can reference shared utilities if needed, but are otherwise isolated from application and domain logic&lt;/li&gt;
&lt;li&gt;banTransitiveDependencies ensures that if lib A depends on lib B which depends on lib C, lib A cannot import from lib C directly — it must go through lib B's public API&lt;/li&gt;
&lt;li&gt;enforceBuildableLibDependencyCheck ensures that if you enable buildable libs, your dependency declarations stay honest&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Advanced Tag Expressions: !, *, and Combining Constraints
&lt;/h2&gt;

&lt;p&gt;One of the most underused and underappreciated features of the @nx/enforce-module-boundaries rule is its support for tag expression operators. Beyond simple string matching, the rule supports negation, wildcards, and compound logic that lets you write precise, expressive constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wildcard Operator: *
&lt;/h2&gt;

&lt;p&gt;The wildcard * matches any tag. This is useful when you want to say "this library can import from literally anything" — which you should use sparingly, but it has legitimate use cases for certain shell or orchestration libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceTag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type:app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"onlyDependOnLibsWithTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More practically, wildcards shine when used within a tag expression for partial matching. For example, if you want to allow a shared utility to depend on any shared library regardless of a sub-classification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;json
{
  "sourceTag": "scope:shared",
  "onlyDependOnLibsWithTags": ["scope:shared", "*:shared"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use ! directly within the &lt;em&gt;onlyDependOnLibsWithTags&lt;/em&gt; array to say "must have this tag and must not have that tag simultaneously":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;json{
  "sourceTag": "scope:accounts",
  "onlyDependOnLibsWithTags": ["scope:accounts", "scope:shared", "!type:e2e"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads as: "accounts-scoped libraries may only depend on libraries that are tagged scope:accounts or scope:shared, and in all cases, those libraries must not be tagged type:e2e." This is particularly useful when your shared scope contains a mix of test utilities and production utilities that you want to distinguish at the dependency constraint level.&lt;/p&gt;

&lt;p&gt;Violations in Action: What This Looks Like in Your IDE&lt;br&gt;
Let us see what happens when a developer violates one of these rules. This is arguably the most important section of the article because it shows the tangible developer experience of boundary enforcement.&lt;br&gt;
typescript// libs/payments/feature-transfer/src/lib/transfer.component.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// ✅ ALLOWED — payments lib importing from its own domain&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PaymentsApiService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@banking/payments/data-access&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ ALLOWED — payments lib importing from shared scope&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CurrencyFormatterPipe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@banking/shared/util-formatters&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ButtonComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@banking/shared/ui-design-system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ VIOLATION — crossing domain boundaries&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoanEligibilityService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@banking/loans/data-access&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ESLint Error: "A project tagged with 'scope:payments' can only depend on &lt;/span&gt;
&lt;span class="c1"&gt;// libs tagged with 'scope:payments' or 'scope:shared'"&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ VIOLATION — lib importing from an app&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@banking/banking-portal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ESLint Error: "A project tagged with 'type:lib' cannot depend on &lt;/span&gt;
&lt;span class="c1"&gt;// libs tagged with 'type:app'"&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ VIOLATION — reaching into e2e project&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MockApiInterceptor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@banking/banking-portal-e2e&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ESLint Error: "Imports of e2e projects are not permitted"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These errors surface in real time in VS Code (with the ESLint extension installed).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architectural Payoff
&lt;/h2&gt;

&lt;p&gt;Once module boundaries are in place and running in CI, the benefits compound over time in ways that go well beyond code organization.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your architecture becomes self-documenting. A new engineer joining the banking platform team can read the ESLint configuration and understand the entire domain structure and dependency rules in under five minutes. The rules tell the same story as the architecture diagram — because they are the architecture diagram, expressed as code.&lt;/li&gt;
&lt;li&gt;Code reviews become faster and more focused. Reviewers no longer need to manually audit import paths or ask questions like "should payments really know about loans?" The linter has already answered that question before the pull request was opened. Reviews can focus entirely on logic, correctness, and intent.&lt;/li&gt;
&lt;li&gt;Refactoring becomes safe. Because every library exposes only what its index.ts exports, and because boundaries prevent cross-domain imports, you can refactor a library's internals with surgical confidence. The blast radius of any change is bounded by the public API surface.&lt;/li&gt;
&lt;li&gt;Teams can work in parallel without stepping on each other. When domain boundaries are enforced, the payments team and the loans team genuinely cannot create accidental dependencies between their work. Conway's Law works in your favour: the organizational structure is mirrored by — and protected by — the module boundary rules.&lt;/li&gt;
&lt;li&gt;The architecture survives team turnover. Senior engineers who understand the original design decisions eventually leave. Without tooling, their architectural intent leaves with them. With boundary enforcement, the decisions are encoded in the linting configuration and outlive any individual contributor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The companion repository for this article, including the full workspace structure, ESLint configuration, sample library implementations, and intentional violations with their error output, is available at &lt;a href="https://github.com/sakthikumaran22/nx-module-boundaries-demo" rel="noopener noreferrer"&gt;github&lt;/a&gt;. Clone it, break the rules, and watch the linter push back.&lt;/p&gt;

&lt;p&gt;github: &lt;a href="https://github.com/sakthikumaran22/nx-module-boundaries-demo" rel="noopener noreferrer"&gt;https://github.com/sakthikumaran22/nx-module-boundaries-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reference: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nx.dev/docs/technologies/eslint/eslint-plugin/guides/enforce-module-boundaries" rel="noopener noreferrer"&gt;https://nx.dev/docs/technologies/eslint/eslint-plugin/guides/enforce-module-boundaries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nx.dev/docs/features/enforce-module-boundaries" rel="noopener noreferrer"&gt;https://nx.dev/docs/features/enforce-module-boundaries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nx</category>
      <category>architecture</category>
      <category>typescript</category>
      <category>monorepo</category>
    </item>
  </channel>
</rss>
