<?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: Felice Lombardi</title>
    <description>The latest articles on DEV Community by Felice Lombardi (@managerfx).</description>
    <link>https://dev.to/managerfx</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%2F1212094%2F6592aa7b-b6d0-43e3-98c7-e22ef0a11a9b.jpeg</url>
      <title>DEV Community: Felice Lombardi</title>
      <link>https://dev.to/managerfx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/managerfx"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Felice Lombardi</dc:creator>
      <pubDate>Mon, 14 Sep 2026 18:37:32 +0000</pubDate>
      <link>https://dev.to/managerfx/-1577</link>
      <guid>https://dev.to/managerfx/-1577</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/managerfx/my-thermostat-was-speaking-an-industrial-protocol-just-not-to-me-2a0p" class="crayons-story__hidden-navigation-link"&gt;My Thermostat Was Speaking an Industrial Protocol. Just Not to Me.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/managerfx/my-thermostat-was-speaking-an-industrial-protocol-just-not-to-me-2a0p" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Software dev bridges hardware gap via ESP32&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/managerfx" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1212094%2F6592aa7b-b6d0-43e3-98c7-e22ef0a11a9b.jpeg" alt="managerfx profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/managerfx" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Felice Lombardi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Felice Lombardi
                
                
              
              &lt;div id="story-author-preview-content-4566043" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/managerfx" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1212094%2F6592aa7b-b6d0-43e3-98c7-e22ef0a11a9b.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Felice Lombardi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/managerfx/my-thermostat-was-speaking-an-industrial-protocol-just-not-to-me-2a0p" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/managerfx/my-thermostat-was-speaking-an-industrial-protocol-just-not-to-me-2a0p" id="article-link-4566043"&gt;
          My Thermostat Was Speaking an Industrial Protocol. Just Not to Me.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/bacnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;bacnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/esp32"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;esp32&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/iot"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;iot&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/managerfx/my-thermostat-was-speaking-an-industrial-protocol-just-not-to-me-2a0p" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;23&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;span class="crayons-story__favorited"&gt;
              &lt;span class="favorited-marker"&gt;
                &lt;span&gt;
                  

                &lt;/span&gt;
                &lt;span class="hidden"&gt;
                  

                &lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;a href="https://dev.to/managerfx/my-thermostat-was-speaking-an-industrial-protocol-just-not-to-me-2a0p#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              10&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            22 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>My Thermostat Was Speaking an Industrial Protocol. Just Not to Me.</title>
      <dc:creator>Felice Lombardi</dc:creator>
      <pubDate>Thu, 03 Sep 2026 15:32:52 +0000</pubDate>
      <link>https://dev.to/managerfx/my-thermostat-was-speaking-an-industrial-protocol-just-not-to-me-2a0p</link>
      <guid>https://dev.to/managerfx/my-thermostat-was-speaking-an-industrial-protocol-just-not-to-me-2a0p</guid>
      <description>&lt;h2&gt;
  
  
  Where I actually started, and it isn't the thermostat
&lt;/h2&gt;

&lt;p&gt;Before telling the story, I need to say who did it, or none of it will make sense.&lt;/p&gt;

&lt;p&gt;I'm a senior software engineer. I can design systems, read a spec, debug ugly problems. About &lt;strong&gt;hardware I knew close to nothing&lt;/strong&gt;. The only program I had ever written for an ESP32 was a loop that blinked some LEDs to a beat: a &lt;code&gt;digitalWrite()&lt;/code&gt; inside a &lt;code&gt;delay()&lt;/code&gt;, and the satisfaction of someone who has just invented fire.&lt;/p&gt;

&lt;p&gt;I had never soldered anything that mattered, never used a multimeter with any real understanding, and the basics of electronics — logic levels, what happens if you feed a module 5V instead of 3.3V, what a differential line even is — I knew only vaguely, the way you know a subject you've never actually had to deal with.&lt;/p&gt;

&lt;p&gt;What I do have is the DIY habit. And there is a very specific satisfaction that people who only write software never fully get to feel: the code you write every day lives inside a screen, and when it works, a green test tells you so. Nice, but abstract.&lt;/p&gt;

&lt;p&gt;When instead the code you wrote runs through a wire you stripped yourself, into a connector you soldered yourself, and on the other end &lt;strong&gt;a physical thing moves&lt;/strong&gt; — a fancoil spins up, a display changes a number, a valve opens — the satisfaction is an order of magnitude bigger. It stops being a program. It becomes an object that does something in the world, and it does it because you put it there.&lt;/p&gt;

&lt;p&gt;And if that object then solves a real, everyday annoyance instead of being a demo you push to GitHub and forget, that's about as good as it gets. It's the only real reason anyone spends fifteen evenings on a thermostat instead of watching a show.&lt;/p&gt;

&lt;p&gt;That said: without AI, this project would not have happened. Not "it would have taken longer" — &lt;strong&gt;it would not have happened&lt;/strong&gt;. I would have stopped at the first six-pin RS-485 module, with nobody to ask whether the bus GND needs to be tied in or not.&lt;/p&gt;

&lt;p&gt;The rest of this article is the story of where what I already knew ends, and where what I learned along the way begins.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&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%2Fwh725u36xyhcaffal9sp.jpg" 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%2Fwh725u36xyhcaffal9sp.jpg" alt="The Delta Controls eZNT-T331 thermostat mounted on the wall" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a Delta Controls eZNT-T331 on my living room wall. It manages three zones and drives the fancoils: cooling in summer, heating in winter. It's done its job reliably for years, without a single hiccup.&lt;/p&gt;

&lt;p&gt;No API. No cloud. No app. And no technical documentation anyone ever handed me.&lt;/p&gt;

&lt;p&gt;What it &lt;strong&gt;does&lt;/strong&gt; have is a serial bus, on which it chats with the system's central control unit twenty-four hours a day.&lt;/p&gt;

&lt;p&gt;It talked to everyone except me. And since the rest of my home is already automated, having to get up and walk to the wall to change a setpoint had become genuinely annoying — the kind of friction that irritates you a little more every single time.&lt;/p&gt;

&lt;p&gt;Swap it for a smart thermostat? Not a workable path. This isn't a standalone appliance: it's part of a centralized HVAC system and talks to a controller that manages several zones together. Replacing it means reworking the interface with that system, involving whoever manages it, paying an integrator, and ending up with yet another closed device anyway.&lt;/p&gt;

&lt;p&gt;The only sensible route was the opposite one: leave the thermostat exactly where it is, and learn to speak its language.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 0: a user manual and some stubbornness
&lt;/h2&gt;

&lt;p&gt;Starting inventory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One wall thermostat.&lt;/li&gt;
&lt;li&gt;Its &lt;strong&gt;user manual&lt;/strong&gt;. The end-user one: buttons, menus, "press here to set holiday mode."&lt;/li&gt;
&lt;li&gt;No datasheet, no protocol spec, no integrator documentation, no credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started by reading the device. Menu by menu, screen by screen: which values it shows, which it lets me change, which change on their own.&lt;/p&gt;

&lt;p&gt;Then the discovery that cracked the whole thing open: there's an &lt;strong&gt;installer menu&lt;/strong&gt;, password-protected. Protected by the default password, naturally. &lt;em&gt;(Thank you, installers of the world. Never change.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Inside that menu was an entry that makes no sense on a standalone appliance: the ability to &lt;strong&gt;change the device's MAC address&lt;/strong&gt;. An address. On a tiny scale, something like "21." Not an Ethernet MAC — a node address on a bus.&lt;/p&gt;

&lt;p&gt;That's when it clicked: this thermostat is a node on a network.&lt;/p&gt;

&lt;p&gt;I went and looked up the model's technical specs online, and the picture completed itself: the eZNT-T331 exists in &lt;strong&gt;two variants&lt;/strong&gt;, one &lt;strong&gt;BACnet/IP&lt;/strong&gt; and one &lt;strong&gt;BACnet MS/TP&lt;/strong&gt;. The IP variant needs an Ethernet connection, and there's none in my wall. MS/TP runs over RS-485 and identifies nodes with exactly the kind of small address I'd just seen in the menu.&lt;/p&gt;

&lt;p&gt;It was the MS/TP variant. And the bus terminals were sitting right there behind the faceplate.&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%2Fjh1fsbkqyc8ljg1d1gwp.jpg" 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%2Fjh1fsbkqyc8ljg1d1gwp.jpg" alt="The eZNT-T331's main board after pulling it off the wall" width="663" height="786"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Two evenings of study, before touching anything
&lt;/h3&gt;

&lt;p&gt;BACnet MS/TP is the building-automation protocol that runs over RS-485 in a &lt;strong&gt;token ring&lt;/strong&gt;: master nodes take turns holding the right to speak, and a node that goes quiet on its turn stalls everyone until the ring re-forms without it.&lt;/p&gt;

&lt;p&gt;Before wiring anything up I studied the protocol: frame format, header CRC and data CRC, token passing, the Poll For Master procedure, the difference between a &lt;em&gt;master&lt;/em&gt; and a &lt;em&gt;slave&lt;/em&gt; node, and the application services I'd need — &lt;code&gt;ReadPropertyMultiple&lt;/code&gt; to read, &lt;code&gt;SubscribeCOV&lt;/code&gt; to get notified of changes.&lt;/p&gt;

&lt;p&gt;I didn't skim, and not out of diligence: I was about to attach a device of my own making to a live bus shared with other devices on the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rule I gave myself, and why
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;If I couldn't explain what a frame does, that frame didn't go on the wire.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's worth explaining where this comes from, because the actual risk isn't where you'd guess.&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;corrupted&lt;/em&gt; frame is the minor problem: MS/TP protects both header and data with two CRCs, and whoever receives garbage simply discards it. On that front the protocol is robust. There are three real ways to cause damage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first is addressing.&lt;/strong&gt; On MS/TP every master has a node address, and they must be unique. If I power up my gateway on an address already held by another master, two nodes end up answering the same token: the ring gets confused, and it isn't just my problem — it's a problem for everyone attached to that bus. That's why I spent my first session with YABE just listening, to see which addresses were already taken, and picked 125 in a free slot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second is timing.&lt;/strong&gt; RS-485 is half-duplex: only one node speaks on the wire at a time, and the token decides who. If I transmit out of turn, my transmission collides with someone else's and I destroy &lt;em&gt;their&lt;/em&gt; frame, not mine. And if I hold the token and leave the bus silent for longer than &lt;code&gt;Tno_token&lt;/code&gt; (500 ms), every other master times out on that silence and starts a Poll For Master cycle to rebuild the ring — it re-forms around me, and I only rejoin once I answer a poll. A slow node doesn't just fail on its own — it slows the whole bus down. This is exactly why, later in the firmware, the core talking to the bus never makes a call that stalls the MS/TP state machine: the FSM has to keep being serviced even while the application is waiting for a reply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third, the most serious, is semantics.&lt;/strong&gt; A frame can be formally perfect and conceptually disastrous. A &lt;code&gt;WriteProperty&lt;/code&gt; is a real write to a real device: if I get the object wrong — or worse, the target — I don't get a compiler error. I change the configuration of a system that other people also depend on. And since I had no documentation, everything I knew about any given object was something I'd deduced myself. So: read for weeks before writing anything, write only to objects whose role I'd actually understood, and only after confirming the effect on my own display.&lt;/p&gt;

&lt;p&gt;Bottom line: the bus isn't mine, and a mistake there isn't paid for by whoever makes it. That's where the rule comes from.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: reconnaissance, or the work AI could not do
&lt;/h2&gt;

&lt;p&gt;To listen to a bus you have to get on it. I pulled the thermostat off the wall, found the two wires A and B — the differential pair RS-485 signaling runs on, the same twisted pair MS/TP travels over — and tapped it: in parallel, non-destructively, so the system's bus kept working while I eavesdropped.&lt;/p&gt;

&lt;p&gt;On the other end, a &lt;strong&gt;Waveshare Industrial USB-to-RS485 converter&lt;/strong&gt;: genuine FT232RL, resettable fuse, TVS diode, ESD protection. This is not the place for a three-euro no-name dongle: that converter sits electrically between my laptop and a live bus shared with other devices, and the protection circuitry is the entire reason you buy it.&lt;/p&gt;

&lt;p&gt;For the record, AI was genuinely useful here: which converter to buy and why, how to tap a differential pair without interrupting the bus, whether to tie in the GND, where the 120 Ω termination lives and why I should care. All things an electrical technician knows and I learned the week before.&lt;/p&gt;

&lt;h3&gt;
  
  
  And then YABE
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;YABE&lt;/strong&gt; stands for &lt;em&gt;Yet Another BACnet Explorer&lt;/em&gt;: an open-source Windows tool that acts as a browser for BACnet networks. You point it at a bus, it sends a discovery message, and it shows you the devices that respond as a navigable tree. For each device you can list the &lt;strong&gt;objects&lt;/strong&gt; it exposes — in BACnet, every value is a typed object, for example &lt;code&gt;AV:10000&lt;/code&gt; (Analog Value number 10000) or &lt;code&gt;MV:90010&lt;/code&gt; (Multi-state Value) — read their properties, and write to them.&lt;/p&gt;

&lt;p&gt;In developer terms: it's Postman for building automation.&lt;/p&gt;

&lt;p&gt;You connect it, hit refresh, and there's that moment every person who's done reverse engineering recognizes: the tree populates, and a black box turns into a list.&lt;/p&gt;

&lt;h3&gt;
  
  
  The actual work
&lt;/h3&gt;

&lt;p&gt;From here on there's no shortcut, and no language model is of any help. I found the objects &lt;strong&gt;myself&lt;/strong&gt;, one at a time, by hand, over several evenings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read a property, write down the value;&lt;/li&gt;
&lt;li&gt;go to the thermostat and physically change something;&lt;/li&gt;
&lt;li&gt;come back, re-read, see what moved;&lt;/li&gt;
&lt;li&gt;guess the meaning, then test the guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;AV:10000&lt;/code&gt; moves when I change zone 1's setpoint. &lt;code&gt;MV:10001&lt;/code&gt; takes four discrete values that map to the fancoil speeds shown on the display. &lt;code&gt;MV:90010&lt;/code&gt; flips when the system switches between heating and cooling season. &lt;code&gt;AV:510&lt;/code&gt; reflects eco status, while &lt;code&gt;BV:90003&lt;/code&gt; &lt;strong&gt;commands&lt;/strong&gt; it and is never written back.&lt;/p&gt;

&lt;p&gt;That last distinction cost me an evening, and later saved me from a bug: &lt;strong&gt;the object you write is often not the object you read.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result was a hand-built CSV: type, instance, my own name for the object, unit, zone, whether it's writable, how often it's worth reading. Around fifty useful objects.&lt;/p&gt;

&lt;p&gt;That CSV became the source of truth for the whole project, and I want to be blunt about this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No language model could have produced that file.&lt;/strong&gt; It has no access to my bus, no knowledge of this device, and no way to correlate a register with a fancoil physically spinning up in my hallway.&lt;/p&gt;

&lt;p&gt;And even if I'd given it that access — an agent turned loose to discover the bus on its own would have flooded it. A shared token ring is not a place you explore by trial and error: every request occupies the channel, and an overeager tool degrades a system that also serves other purposes. This called for surgery and patience: one reading at a time, hand on the brake.&lt;/p&gt;

&lt;p&gt;That part I did myself. And that's exactly why everything after it moved fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: from sniffer to gateway
&lt;/h2&gt;

&lt;p&gt;A laptop running YABE is a diagnostic tool, not an integration. I needed something stable, permanently on the bus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First prototype: ESP32-WROOM-32E plus an XY-S485 module.&lt;/strong&gt; The XY-S485 handles direction automatically, so there's no DE/RE pin to toggle in software (on many cheap RS-485 converters there's a pin, called DE/RE, that you have to drive yourself to tell the chip "now I'm transmitting" or "now I'm listening"; get that timing wrong by a hair and you corrupt the frame): one less real-time trap on a protocol where mistiming corrupts someone else's frame. UART2 on GPIO16/17, ground shared with the bus, jumper wires everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By the second evening it was reading and writing real values on a live bus.&lt;/strong&gt; Not a demo, not a mock: genuine &lt;code&gt;ReadPropertyMultiple&lt;/code&gt; responses from the actual thermostat.&lt;/p&gt;

&lt;p&gt;That matters more than it sounds. From that point the project was never in a broken state again: every following step was an increment on something that already worked, revertible with a single commit if it made things worse. Never a big-bang integration.&lt;/p&gt;

&lt;p&gt;Later I ported everything to a &lt;strong&gt;Seeed XIAO ESP32S3&lt;/strong&gt;: 21 × 17.8 mm, 8 MB flash, 8 MB PSRAM, native USB CDC. The port hid one genuine trap worth documenting, because it will bite anyone moving code from a classic ESP32 to an S3:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On the ESP32-S3, &lt;strong&gt;GPIO16 and GPIO17 are not the UART2 pins&lt;/strong&gt;. They're wired to the OPI PSRAM and aren't even exposed on the board. UART2 gets remapped through the GPIO matrix to D6/D7, i.e. GPIO43/44.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Phase 3: the firmware
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;🔧 &lt;strong&gt;Technical chapter — skip to Phase 4 if you don't care about the implementation detail.&lt;/strong&gt;&lt;br&gt;
As an engineer, though, I couldn't get away with "and then I wrote the firmware": these &lt;br&gt;
decisions are the reason the gateway sits on a shared bus without bothering anyone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The finished gateway does more than shovel bytes from one side to the other. A few design decisions are worth pulling out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two cores, with a rule written on top
&lt;/h3&gt;

&lt;p&gt;The ESP32-S3 has two cores, and MS/TP does not forgive latency: go quiet while holding the token and the bus goes quiet with you, until the other masters time out on the silence and rebuild the ring around you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core 1, &lt;code&gt;bacnet_task&lt;/code&gt;&lt;/strong&gt;: the MS/TP state machine and the BACnet scheduler, in a tight loop. Never a call that stalls the FSM: it keeps being serviced even while a request is in flight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core 0, &lt;code&gt;network_task&lt;/code&gt;&lt;/strong&gt;: WiFi, the async web server, MQTT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traffic between the two cores passes through exactly two channels: a mutex-protected &lt;code&gt;DataCache&lt;/code&gt;, and a write queue into the scheduler. This is &lt;strong&gt;written down in black and white&lt;/strong&gt; as a hard rule, and I'll come back to why writing it down changed everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  One BACnet operation per tick
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;BACnetScheduler::tick()&lt;/code&gt; performs &lt;strong&gt;at most one&lt;/strong&gt; bus operation per call: no loops inside a tick, and no chaining one request after another. It runs a four-phase state machine — &lt;code&gt;BOOT_WAIT&lt;/code&gt; → &lt;code&gt;UNSUB_ALL&lt;/code&gt; → &lt;code&gt;SEED_RPM&lt;/code&gt; → &lt;code&gt;READY&lt;/code&gt; — and once in &lt;code&gt;READY&lt;/code&gt; it alternates four sub-operations, one per tick: drain the post-write refresh queue, reconcile one COV subscription, read one object in round-robin, renew one expiring subscription.&lt;/p&gt;

&lt;p&gt;The constraint is deliberate, but it's worth being precise about what it buys, because I first got this wrong myself: it is not what protects the token ring. That's the state machine's job — &lt;code&gt;Treply_timeout&lt;/code&gt; caps the wait for a reply at 250 ms, &lt;code&gt;Nmax_info_frames = 1&lt;/code&gt; forces the token onward after a single info frame, and the 501-octet cap on frame data holds a full frame to 511 octets, roughly 67 ms of wire time. Worst-case token hold therefore lands around 385 ms against a 500 ms &lt;code&gt;Tno_token&lt;/code&gt;, no matter how much payload the operation carries. What one-operation-per-tick bounds is scheduler latency: a single operation can block for up to 2.5 s at the application level, so doing one per tick is what keeps the refresh queue, the COV reconciliation, the round-robin read and the renewal from crowding each other out.&lt;/p&gt;

&lt;h3&gt;
  
  
  A cap on properties per request
&lt;/h3&gt;

&lt;p&gt;The rule that &lt;em&gt;does&lt;/em&gt; map onto the frame-size constraint is a different one. &lt;code&gt;ReadPropertyMultiple&lt;/code&gt; is the operation whose cost is set by how much you packed into it rather than by being one thing, so batches are capped at 12 objects — chosen so the &lt;em&gt;response&lt;/em&gt; stays under the 501-octet MS/TP data limit. The request also declares max-APDU-accepted = 480 with segmented-response-accepted clear, so the device can't legally answer with a segmented reply, and every RPM call site in the codebase is a compile-time constant over a static array. If a reply came back oversized anyway, the receive FSM hits &lt;code&gt;FrameTooLong&lt;/code&gt; and skips the data: the cost is a failed read and a backoff retry, not a dropped token.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to &lt;a class="mentioned-user" href="https://dev.to/vinhnguyenthanhdn"&gt;@vinhnguyenthanhdn&lt;/a&gt; in the comments for pressing on the distinction between the two invariants.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Budgeted COV subscriptions
&lt;/h3&gt;

&lt;p&gt;Change-of-Value subscriptions are how you get notified of changes instead of asking over and over. The eZNT-T331 accepts &lt;strong&gt;at most 12 simultaneous subscriptions&lt;/strong&gt; — a device limit, and one of the first things I measured.&lt;/p&gt;

&lt;p&gt;So subscriptions are a budget to spend, not a resource you can assume. Every object in the registry declares its own condition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CC_NONE&lt;/code&gt; — never subscribed, read only via round-robin;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CC_FE_ACTIVE&lt;/code&gt; — subscribed only while a browser is actually connected;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CC_FE_ACTIVE_AREA_ENABLED&lt;/code&gt; — subscribed only if a browser is connected &lt;strong&gt;and&lt;/strong&gt; that zone is enabled in the device's configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At rest, with nobody watching, the gateway holds &lt;strong&gt;zero&lt;/strong&gt; subscriptions and falls back to round-robin polling. Open the web UI and subscriptions spin up; close it and they're cancelled 30 seconds later. The system's bus carries no traffic on my behalf that nobody is looking at.&lt;/p&gt;

&lt;h3&gt;
  
  
  The registry is the single source of truth
&lt;/h3&gt;

&lt;p&gt;That hand-built CSV compiles into a PROGMEM table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;OT_AV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"A1_StpEffettivo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"area1/setpoint_effective"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PI_5MIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DOM_STATUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CC_FE_ACTIVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type, instance, internal name, MQTT subtopic, unit, zone, poll interval, domain, COV condition, read-only flag. &lt;strong&gt;Nowhere else in the codebase is a BACnet instance number hardcoded.&lt;/strong&gt; Not in the web layer, not in MQTT, not in the frontend. The table today holds 56 objects, 11 of them COV-capable.&lt;/p&gt;

&lt;h3&gt;
  
  
  A lean data contract
&lt;/h3&gt;

&lt;p&gt;The Angular frontend polls the gateway once a second, so the payload had to be small. Two endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /schema&lt;/code&gt; — fetched once: names, units, types, ids;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /status?since=&amp;lt;seq&amp;gt;&lt;/code&gt; — a positional array of values plus a sequence counter, and an &lt;strong&gt;HTTP 304&lt;/strong&gt; when nothing has changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No key repeated on every response, no JSON object per value. On an idle system, a poll cycle costs a 304 and nothing else.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 4: where AI earned its keep
&lt;/h2&gt;

&lt;p&gt;Now the part everyone actually wants to read about. I want to be precise here, because "I built this with AI" is doing a lot of unearned work in most posts.&lt;/p&gt;

&lt;p&gt;I didn't chat with a model and paste snippets. I built an &lt;strong&gt;environment&lt;/strong&gt; for the agent to work inside. Four pieces.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. An architectural contract the agent reads before every task
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; at the repo root, holding the invariants that are expensive to rediscover and disastrous to violate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BACnet operations live in &lt;code&gt;bacnet_task&lt;/code&gt;, network and MQTT in &lt;code&gt;network_task&lt;/code&gt;, no exceptions;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DataCache::update()&lt;/code&gt; is the only safe path for writing across cores;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;object_registry&lt;/code&gt; is the single source of truth: never hardcode an instance or type anywhere else;&lt;/li&gt;
&lt;li&gt;C++17, no dynamic allocation after &lt;code&gt;setup()&lt;/code&gt; — static, stack, or PROGMEM;&lt;/li&gt;
&lt;li&gt;the frontend is zoneless Angular with signals, no RxJS for UI state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the highest-leverage file in the whole project. An agent that &lt;em&gt;knows&lt;/em&gt; it can't allocate on the heap after &lt;code&gt;setup()&lt;/code&gt; writes different code from one that doesn't. Not because it's smarter — because the constraint is &lt;strong&gt;present&lt;/strong&gt; instead of guessed at. A good chunk of what we call AI hallucination, on a real codebase, is an agent doing something reasonable in a context where nobody told it the rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Custom skills for repetitive, error-prone work
&lt;/h3&gt;

&lt;p&gt;The riskiest routine operation in this project is regenerating the firmware registry when the CSV changes. Do it by hand and sooner or later you'll typo an instance number, and you'll find out weeks later, when a value is silently wrong.&lt;/p&gt;

&lt;p&gt;So it became a skill, &lt;code&gt;/regen-registry&lt;/code&gt;, with an explicit procedure: parse the CSV, parse the current table, diff them by &lt;code&gt;type:instance&lt;/code&gt;, list added/removed/changed, apply, update &lt;code&gt;REGISTRY_SIZE&lt;/code&gt;, run the invariant checks and the build, and present the diff for human review.&lt;/p&gt;

&lt;p&gt;The skill's own text carries judgment earned the hard way, including this line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do NOT drop in-domain objects on a hunch that "the FE does not use them".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a mistake made once, fixed once, and then written down so it can't happen again. Note also the last step: &lt;strong&gt;never commit without human review&lt;/strong&gt;, because generation isn't deterministic. The skill automates the tedium, not the accountability.&lt;/p&gt;

&lt;p&gt;A second skill, &lt;code&gt;/add-fe-icon&lt;/code&gt;, handles adding an icon to the pre-bundled offline icon set — small, boring, and exactly the kind of multi-file ritual that's easy to half-do.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Plan first, then code — with subagents in parallel
&lt;/h3&gt;

&lt;p&gt;For anything non-trivial the flow was: &lt;strong&gt;spec → written plan → execution → code review&lt;/strong&gt;, with parallel subagents doing exploration, planning, and review as distinct passes instead of one long conversation.&lt;/p&gt;

&lt;p&gt;The repo holds &lt;strong&gt;16 implementation plans and 11 design specs&lt;/strong&gt;, committed alongside the code. Not documentation written afterward: documents written &lt;strong&gt;before&lt;/strong&gt;, reviewed by me, then executed. When the OTA update feature required several sessions across several evenings, each resumption started by re-reading the plan instead of reconstructing my intent from a diff.&lt;/p&gt;

&lt;p&gt;There's also an &lt;strong&gt;ADR&lt;/strong&gt; (&lt;em&gt;Architecture Decision Record&lt;/em&gt;, a short document that writes down an architectural choice, the alternatives considered, and the reasoning behind the decision) recording a design that was evaluated and &lt;strong&gt;rejected&lt;/strong&gt;: an SSE-driven COV subscription lifecycle instead of one driven by the web UI's polling. Writing down what you decided against is worth as much as writing down what you chose — especially when the thing rediscovering it in a month is a model with no memory of the conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Tests as the verification loop
&lt;/h3&gt;

&lt;p&gt;This is the piece that makes everything else safe.&lt;/p&gt;

&lt;p&gt;All the genuinely tricky logic is factored into &lt;strong&gt;pure modules with no Arduino dependency&lt;/strong&gt;, unit-tested with Unity in PlatformIO's native environment: the COV reconciler, the post-write refresh ordering, the season-change detector, the MS/TP CRC, the master-node state machine, the receive state machine, the frame router, the status payload serializer, the OTA chunk planner and its header parser, the status-LED state machine, the write guard, the setpoint limits, the token verifier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20 test suites, all running on the host in under a second.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The point isn't coverage as a virtue. The point is that an agent proposing a change to subscription reconciliation gets an &lt;strong&gt;immediate, objective&lt;/strong&gt; verdict without touching hardware. AI-generated code and a fast test suite are close to a symbiotic pair: if generating is cheap, verifying has to be cheaper still. Without that suite, every change costs a flash cycle and a walk to the electrical panel.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hardest thing it did
&lt;/h3&gt;

&lt;p&gt;The MS/TP layer I'd originally written was a simplified implementation that assumed a quiet bus. My bus isn't quiet — there are several other active masters on it, and a simplified implementation on a busy token ring is a slow-motion failure: it works for days, then starts dropping in ways you can't explain.&lt;/p&gt;

&lt;p&gt;The fix was to port a real &lt;strong&gt;ASHRAE 135 Clause 9.5.6 master node state machine&lt;/strong&gt; from bacnet-stack: the receive FSM, the master node FSM, token passing, Poll For Master, frame assembly, both CRCs. Vendored as pure C, kept free of Arduino dependencies, tied to the hardware through a thin callback seam, and covered by host tests before it ever touched the bus.&lt;/p&gt;

&lt;p&gt;That's legitimately hard embedded work for anyone, let alone someone who six months earlier was blinking LEDs to a beat. With the plan written first, the port structured as its own vendored library, and the state machine tested on the host, it cost evenings, not weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 5: the bug that sums it all up
&lt;/h2&gt;

&lt;p&gt;The most instructive failure of the whole project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first write after boot always timed out. Every subsequent write was fine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a logic error, not a protocol error. The BACnet layer was correct, and I could prove it: the state machine's tests passed on the host.&lt;/p&gt;

&lt;p&gt;The cause: &lt;strong&gt;AsyncTCP spawns its task with no core affinity and a high priority.&lt;/strong&gt; When the first HTTP request arrived, that task got scheduled onto Core 1 and preempted &lt;code&gt;bacnet_task&lt;/code&gt; — the one task in the whole system that must never be interrupted. The gateway lost the token mid-transaction, and the write died with it.&lt;/p&gt;

&lt;p&gt;The fix is a single build flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;-D&lt;/span&gt; &lt;span class="py"&gt;CONFIG_ASYNC_TCP_RUNNING_CORE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin AsyncTCP to Core 0, where the rest of the networking already lives, and Core 1 belongs to MS/TP alone.&lt;/p&gt;

&lt;p&gt;I keep this bug as the honest summary of the whole collaboration. Finding it required knowing that a token ring has hard timing, that a third-party library ships its own task, and that FreeRTOS will happily schedule that task on whichever core it likes. The first piece I knew because I'd studied the protocol; the other two I learned right there.&lt;/p&gt;

&lt;p&gt;But narrowing it down &lt;strong&gt;systematically&lt;/strong&gt; — ruling out the protocol layer because the tests passed, correlating the failure with the first HTTP request, then going and reading how that library creates its task — was a debugging loop the agent drove faster than I would have on my own.&lt;/p&gt;

&lt;p&gt;Neither half would have gotten there alone in one evening.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 6: Home Assistant, and the electrical panel
&lt;/h2&gt;

&lt;p&gt;The gateway exposes three surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;a local web UI&lt;/strong&gt; — the watchword is lightness: Angular 21, standalone components, zoneless, signals only; built, gzipped, and served straight from LittleFS in just three files, because the gateway's flash memory isn't a server's;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a REST API&lt;/strong&gt; — &lt;code&gt;/schema&lt;/code&gt;, &lt;code&gt;/status&lt;/code&gt;, &lt;code&gt;/write&lt;/code&gt;, plus the OTA endpoints;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MQTT with Home Assistant discovery&lt;/strong&gt; — the thermostat shows up as a native &lt;code&gt;climate&lt;/code&gt; entity, with the right modes, fan speeds, setpoint limits, and current HVAC action; plus a custom Lovelace card for the three-zone layout the stock card doesn't cover.&lt;/li&gt;
&lt;/ul&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%2F97n7juiryj9zd35xpwtc.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%2F97n7juiryj9zd35xpwtc.png" alt="The gateway's Angular web UI" width="800" height="628"&gt;&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%2F69gx1qlacvpl93y4ct66.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%2F69gx1qlacvpl93y4ct66.png" alt="The thermostat as a climate entity inside Home Assistant" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the things that turn a prototype into an appliance: a &lt;strong&gt;weekly schedule editor&lt;/strong&gt; — from the web UI you set, day by day, which time bands the thermostat should follow its normal program in (I use it for presence/absence bands, without ever touching the physical buttons on the wall again); firmware updates &lt;strong&gt;over the network&lt;/strong&gt;, so when I find a bug I don't have to open the electrical panel and reconnect the ESP32-S3 over USB, I just push the new firmware from a browser; and a status LED whose blink rhythm encodes AP mode, STA connected, scheduler ready, and device reachable — one monochrome LED, the state lives in the duty cycle, and the logic that decides it is pure and unit-tested.&lt;/p&gt;

&lt;p&gt;Then the physical finish, which took as long as any feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;permanent tap&lt;/strong&gt; on the MS/TP pair behind the thermostat: parallel, non-invasive, the shared bus untouched;&lt;/li&gt;
&lt;li&gt;the cable &lt;strong&gt;routed through the wall to the electrical panel&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;dedicated power supply&lt;/strong&gt; for the ESP32-S3 inside the panel, so the gateway isn't hanging off a USB charger in a hallway outlet;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;DIN rail enclosure, designed in CAD and 3D printed&lt;/strong&gt;, because nothing off the shelf fits a 21 × 17.8 mm board plus a converter module and a strain-relieved bus tap. For the design I had to call in a friend (thank’s Nicola 🙂) who does mechanical design — my own CAD skills run out well before a DIN clip that has to snap in to the millimeter — and watching his 3D model turn into a real part, custom-fit to &lt;em&gt;my&lt;/em&gt; problem, was another huge satisfaction, mostly his and a little bit mine.&lt;/li&gt;
&lt;/ul&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%2Fc4iuej1cs2tct39axyxs.jpg" 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%2Fc4iuej1cs2tct39axyxs.jpg" alt="The SolidWorks model of the DIN case, designed by a friend" width="800" height="935"&gt;&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%2Fvp01g3xhsv5x2rlpueax.jpg" 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%2Fvp01g3xhsv5x2rlpueax.jpg" alt="The printed case mounted on the DIN rail inside the electrical panel, with its dedicated power supply" width="759" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From a breadboard on the kitchen table to a component on a DIN rail. That last 10% is where hobby projects usually stop, and it's the entire difference between a demo and something you forget you even have, because it just works.&lt;/p&gt;

&lt;p&gt;And the soldering, I did myself. The first attempt was with a Parkside iron from Lidl, which mostly succeeded in sending an ESP32-S3 up in smoke — and taught me that a fifteen-euro iron has no temperature control at all, so it either doesn't get hot enough or it cooks the component. After that I bought the real gear: a temperature-controlled station, decent tips, flux, desoldering braid, a helping-hands jig. Now I have more of it than I need, as tradition demands.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'm taking away from this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The numbers:&lt;/strong&gt; ~15 hours total, spread across 15 evenings. Working prototype on the second evening. 279 commits. 56 BACnet objects. 20 test suites on the host. 16 plans, 11 specs, 1 rejected design recorded for posterity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lessons:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bring what you know, let yourself be taught the rest.&lt;/strong&gt; My contribution was the method: read the spec, isolate the logic, never put anything on the bus you couldn't explain, keep the system always working. The hardware — the wiring, the logic levels, the soldering iron — I learned during the project, with AI as an infinitely patient tutor. Neither half was enough on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some things stay yours.&lt;/strong&gt; The bus tap, the eyedropper-careful sniffing session, the hand-built CSV: not delegable, and not out of pride. An agent turned loose on a shared system floods it; that part called for meticulousness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invest in the setup, not the prompt.&lt;/strong&gt; The architectural contract, the custom skills, the plan-before-code workflow, tests that run in a second on the host. Every hour spent there paid back many times over. The prompt is the least interesting part of working with an agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write mistakes down where they can't recur.&lt;/strong&gt; Every rule in my &lt;code&gt;CLAUDE.md&lt;/code&gt; and every warning in my skills is a scar. Written down, scars become guardrails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make verification cheaper than generation.&lt;/strong&gt; Generated code costs almost nothing; that only helps if checking it costs even less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get to something working by day two, and never let it be broken again.&lt;/strong&gt; Fifteen one-hour sessions only work if each one starts from something that runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down what you rejected, too.&lt;/strong&gt; Otherwise future-you — and any agent reading the repo — will propose it again with great enthusiasm.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The thermostat on the wall looks exactly like it did six months ago. It just answers to Home Assistant now. And the interesting part was never the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  One last, less technical thought
&lt;/h2&gt;

&lt;p&gt;There's something this project made clear to me, and it's bigger than the thermostat.&lt;/p&gt;

&lt;p&gt;For years, technical competence has also functioned as a barrier to entry: to do serious hardware you had to have studied electronics, to port an industrial protocol onto a microcontroller you had to have read hundreds of pages of spec, to 3D print a custom mechanical part you had to know CAD. People without that background stayed out, rightly, or relied on someone who had it.&lt;/p&gt;

&lt;p&gt;AI hasn't eliminated that competence: someone, somewhere, still has to know how a token ring works, what a core-affinity build flag does, how to size a DIN clip. What it eliminated is the requirement that that someone always has to be &lt;em&gt;me&lt;/em&gt;, and exactly when I need it. It compressed the distance between "I have an idea" and "the idea exists" — for anyone who still brings a method: the ability to verify, to not trust blindly, to know when an answer sounds wrong even before you can say why.&lt;/p&gt;

&lt;p&gt;It isn't the democratization of expertise. It's something more modest and more useful: shortening the distance between a software engineer's curiosity and the piece of metal that ends up, working, inside an electrical panel. For someone with DIY in their blood but not on their résumé, that's not a small thing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And here's a provocation I'll aim at myself before someone aims it at me in the comments: in ten years, how much of this will still be done by a human? The reverse engineering with a soldering iron in hand, the bus tap, the custom-printed part from a friend's CAD file — today those are still mine because an agent has no hands, no eyes on a multimeter, can't reach behind a wall plate to tap a twisted pair. But "can't yet" and "will never" are different sentences, and the gap between them is closing faster than I'd like to admit: manipulation robotics, vision, agents driving physical tools. The day a robot can hold a soldering iron with the same care an agent today holds a code review, the "mine" part of this story moves further downstream — toward deciding &lt;em&gt;what&lt;/em&gt; to build and &lt;em&gt;why&lt;/em&gt;, no longer &lt;em&gt;how&lt;/em&gt;. That doesn't scare me; it's the same shift I already lived through writing fewer lines of code by hand and more lines of intent. But it would be dishonest to pretend it stops here.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Happy to go deeper on any piece — the MS/TP master state machine port, the conditional COV budgeting, or the skill definitions. Ask in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bacnet</category>
      <category>esp32</category>
      <category>iot</category>
      <category>ai</category>
    </item>
    <item>
      <title>What is NOT a Micro-Frontend: Clearing the Confusion</title>
      <dc:creator>Felice Lombardi</dc:creator>
      <pubDate>Fri, 13 Jun 2025 12:57:09 +0000</pubDate>
      <link>https://dev.to/managerfx/cosa-non-e-un-micro-frontend-facciamo-chiarezza-5lh</link>
      <guid>https://dev.to/managerfx/cosa-non-e-un-micro-frontend-facciamo-chiarezza-5lh</guid>
      <description>&lt;p&gt;As a Senior Frontend Architect, I've witnessed countless discussions about micro-frontends where teams mistakenly identify architectural patterns, leading to poor decisions and technical debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Pain Point: The Tragedy of Misunderstood Architecture
&lt;/h2&gt;

&lt;p&gt;Let me tell you a story that still haunts me. I was in a crucial architectural meeting where we were deciding the future of our frontend strategy. The lead architect confidently presented their "micro-frontend solution" – and I watched in horror as they described what was essentially a glorified component library.&lt;/p&gt;

&lt;p&gt;"We'll create reusable components," they enthusiastically explained, "the Login component, the Dashboard component, the UserProfile component. Each team can own their components, and we'll have micro-frontends!"&lt;/p&gt;

&lt;p&gt;Fortunately, I managed to catch the problem in time. After hours of discussion and presenting concrete examples, I was able to convince them of the true meaning of what a micro-frontend entails. We'll explore what that means later.&lt;/p&gt;

&lt;p&gt;But that wasn't the worst of it. In another project, I witnessed pure madness: an architect seriously proposing to compose entire pages using multiple iframes. "We'll have the header iframe, the sidebar iframe, the main content iframe, and the footer iframe," they said with a straight face. "Each team can own its iframe!"&lt;/p&gt;

&lt;p&gt;Thankfully, a few days later, they walked back their proposal entirely.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article aims to prevent that pain by clearly defining what micro-frontends are NOT, so you can avoid these painful mistakes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's clarify what micro-frontends truly are by examining what they are NOT.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Micro-Frontends Are NOT
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ NOT Just Multiple SPAs Behind a Reverse Proxy
&lt;/h3&gt;

&lt;p&gt;Many developers think that having several Single Page Applications (SPAs) running on different ports and routing them through a reverse proxy creates a micro-frontend architecture. This is one of the most common misconceptions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This is NOT a micro-frontend architecture&lt;/span&gt;
&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/dashboard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://react-dashboard:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/profile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://vue-profile:3001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/billing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://angular-billing:3002&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;&lt;strong&gt;Why it fails:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full page reloads&lt;/strong&gt;: When users navigate from &lt;code&gt;/dashboard&lt;/code&gt; to &lt;code&gt;/profile&lt;/code&gt;, the entire page reloads, breaking the single-page application experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No shared state&lt;/strong&gt;: User authentication, cart data, or any global state is lost between applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconsistent UI&lt;/strong&gt;: Each app loads its own CSS, fonts, and UI components, leading to visual flickering and inconsistent design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance issues&lt;/strong&gt;: Each transition requires downloading new JavaScript bundles, CSS files, and fresh API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser history problems&lt;/strong&gt;: Back button behavior becomes unpredictable across different applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real-world analogy&lt;/strong&gt;: It's like having separate websites pretending to be a single application – imagine if Gmail reloaded the entire page every time you switched from Inbox to Compose!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  ❌ NOT a Component Library or Design System
&lt;/h3&gt;

&lt;p&gt;Another common confusion happens when developers think that using shared UI components from a design system equates to micro-frontends. While component libraries are valuable, they are fundamentally different from micro-frontend architecture.&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="c1"&gt;// This is a component library, NOT a micro-frontend&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;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DataTable&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;@company/ui-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserManagement&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleCreate&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;Create&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DataTable&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;&lt;strong&gt;Why it fails:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared dependencies&lt;/strong&gt;: All teams must use the same version of the component library, creating coordination overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No independent deployments&lt;/strong&gt;: If the component library changes, all applications must update and redeploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single point of failure&lt;/strong&gt;: A bug in the shared component affects every application that uses it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited team autonomy&lt;/strong&gt;: Teams cannot make independent technology choices or significantly customize components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not business-focused&lt;/strong&gt;: Components are UI building blocks, not complete business functionalities.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Think of it this way&lt;/strong&gt;: Component libraries are like LEGO bricks that everyone shares, but micro-frontends are like complete LEGO sets (spaceship, castle, car) that different teams build and can swap in and out of a larger city.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  ❌ NOT iframe-Based Solutions
&lt;/h3&gt;

&lt;p&gt;While iframes technically allow embedding different applications, they create more problems than they solve and represent an outdated approach that many beginners mistakenly consider "micro-frontends."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"app-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard-app"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"500px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/analytics-app"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"400px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it fails:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Styling nightmare&lt;/strong&gt;: Each iframe has its own CSS context, making it impossible to create consistent spacing, fonts, and colors across applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communication barriers&lt;/strong&gt;: Parent and child applications struggle to share data or coordinate actions (like showing loading states).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility issues&lt;/strong&gt;: Screen readers and keyboard navigation break across iframe boundaries, failing users with disabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance bottlenecks&lt;/strong&gt;: Each iframe loads a complete HTML document with its own CSS and JavaScript, consuming unnecessary resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor mobile responsiveness&lt;/strong&gt;: Iframes do not play well on mobile devices, leading to scrolling issues and touch interaction problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO challenges&lt;/strong&gt;: Search engines have difficulty correctly indexing content within iframes.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real-world comparison&lt;/strong&gt;: Using iframes is like having separate TV screens for each room in your house instead of having one smart TV that can display different content – technically possible but clunky and inefficient.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  ❌ NOT Monorepos with Multiple Apps
&lt;/h3&gt;

&lt;p&gt;Many developers assume that organizing multiple applications within a single repository (monorepo) automatically creates a micro-frontend architecture. This is a fundamental misunderstanding of what micro-frontends represent.&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;"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;"company-apps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspaces"&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;"apps/dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"apps/admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"apps/mobile"&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;&lt;strong&gt;Why it fails:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deployment coupling&lt;/strong&gt;: Even if apps are separate, they are often deployed together, defeating the purpose of independent releases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared build process&lt;/strong&gt;: All applications typically use the same build tools, Node.js version, and deployment pipeline, limiting technology choices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code organization ≠ Runtime integration&lt;/strong&gt;: Having separate folders doesn't mean the applications work together as a unified experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing runtime composition&lt;/strong&gt;: Users still navigate between entirely separate applications rather than seamlessly integrated functionalities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordination overhead&lt;/strong&gt;: Teams still need to coordinate changes, updates, and releases because everything lives in the same repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Simple analogy&lt;/strong&gt;: A monorepo with multiple apps is like having separate apartments in the same building – they are organized together but are still completely independent living spaces. Micro-frontends are more like having different rooms in the same house that work together to create a single living experience.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  ❌ NOT Route-Based Code Splitting
&lt;/h3&gt;

&lt;p&gt;This is probably the most subtle misconception. Many developers believe that splitting their application's code based on routes and dynamically loading them creates micro-frontends. While code splitting is an excellent optimization technique, it's not the same as micro-frontend architecture.&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="c1"&gt;// This is code splitting, NOT micro-frontend&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&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;./Dashboard&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;Profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&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;./Profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Profile&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Routes&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Router&lt;/span&gt;&lt;span class="err"&gt;&amp;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;&lt;strong&gt;Why it fails:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single deployment unit&lt;/strong&gt;: All code splitting happens within one application that is deployed as a single unit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single team ownership&lt;/strong&gt;: One team still owns the entire application, including all routes and functionalities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared dependencies&lt;/strong&gt;: All route components use the same React version, same build tools, same everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build-time splitting&lt;/strong&gt;: The splitting happens when you build the app, not when different teams deploy their functionalities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technology lock-in&lt;/strong&gt;: You can't have Dashboard in React and Profile in Vue – everything must use the same technology stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordination required&lt;/strong&gt;: Adding new routes or changing shared logic still requires coordination with the entire team.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Easy way to remember&lt;/strong&gt;: Code splitting is like having a large closet with organized sections (shirts, pants, shoes) – it's still one closet owned by one person. Micro-frontends are like having separate wardrobes that different people own and manage, but they all contribute to a unified clothing experience.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Micro-Frontends Really Are
&lt;/h2&gt;

&lt;p&gt;Micro-frontends are &lt;strong&gt;independently deployable frontend applications&lt;/strong&gt; that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work together in a single page/browser context.&lt;/li&gt;
&lt;li&gt;Are owned by different teams with full autonomy.&lt;/li&gt;
&lt;li&gt;Are deployed independently without coordination.&lt;/li&gt;
&lt;li&gt;Are integrated at runtime using composition techniques.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ A True Micro-Frontend Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Shell application (container)&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;registerApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&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;single-spa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;registerApplication&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;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&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;System&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@company/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;activeWhen&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;/dashboard&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;registerApplication&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;user-profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&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;System&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@company/user-profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;activeWhen&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;/profile&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;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Individual micro-frontend (dashboard team)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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;react&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="nx"&gt;ReactDOM&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;react-dom&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="nx"&gt;singleSpaReact&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;single-spa-react&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;Dashboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="nx"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lifecycles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;singleSpaReact&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rootComponent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dashboard&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;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unmount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lifecycles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Key Indicators You're Doing Micro-Frontends Correctly
&lt;/h3&gt;

&lt;p&gt;✅ Different teams can deploy independently.&lt;br&gt;
✅ Applications seamlessly integrate at runtime.&lt;br&gt;
✅ Teams can choose different technologies.&lt;br&gt;
✅ Shared navigation and consistent UX.&lt;br&gt;
✅ Applications can communicate when necessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  When NOT to Use Micro-Frontends
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Small teams (&amp;lt; 3 frontend teams)&lt;/li&gt;
&lt;li&gt;Simple applications&lt;/li&gt;
&lt;li&gt;Tight coupling requirements&lt;/li&gt;
&lt;li&gt;Performance is hyper-critical&lt;/li&gt;
&lt;li&gt;Limited infrastructure resources&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Micro-frontends solve organizational problems, not technical ones. They enable team autonomy and independent deployment at the cost of increased complexity. Before adopting them, ensure you have the team structure and organizational needs that justify the architectural overhead.&lt;/p&gt;

&lt;p&gt;The key is to understand that micro-frontends are about &lt;strong&gt;runtime integration of independently deployed applications&lt;/strong&gt;, not just splitting code or using multiple technologies.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Definitive Definition
&lt;/h2&gt;

&lt;p&gt;To close this discussion, I'll share the definition from Luca Mezzalira, one of the most respected voices in the micro-frontend community and author of "Building Micro-Frontends":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Micro-frontends are the technical representation of a business subdomain. They allow independent implementation with the same or different technology. Finally, they should minimise the code shared and owned by other subdomains owned by a single team."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This definition perfectly captures the essence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus on business subdomain&lt;/strong&gt;: Not technical functionalities, but complete business capabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong boundaries&lt;/strong&gt;: Clear separation of concerns and responsibilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear contracts&lt;/strong&gt;: Well-defined interfaces for communication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No shared logic&lt;/strong&gt;: True independence between different subdomains.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When evaluating whether you're building true micro-frontends, ask yourself: "Does this represent a complete business subdomain with its own team, deployment cycle, and clear boundaries?" If the answer is no, you're likely dealing with one of the anti-patterns we've discussed.&lt;/p&gt;




&lt;p&gt;What's your experience with micro-frontends? Have you seen these anti-patterns in your organization? Share your thoughts in the comments below! 👇&lt;/p&gt;

</description>
      <category>microfrontends</category>
      <category>architecture</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Angular vs. Blazor WASM: My Architectural Battle for Performance and Scalability!⚔️</title>
      <dc:creator>Felice Lombardi</dc:creator>
      <pubDate>Fri, 06 Jun 2025 13:50:29 +0000</pubDate>
      <link>https://dev.to/managerfx/da-angular-a-blazor-wasm-un-viaggio-nel-frontend-3b36</link>
      <guid>https://dev.to/managerfx/da-angular-a-blazor-wasm-un-viaggio-nel-frontend-3b36</guid>
      <description>&lt;p&gt;As a Technical Architect with years of experience in Angular, I dove headfirst into Blazor WebAssembly. The result? A comparative analysis that goes beyond a simple "what does what": it's a story of how these two platforms tackle the most critical challenges for any architect: &lt;strong&gt;performance&lt;/strong&gt;, &lt;strong&gt;build optimizations&lt;/strong&gt;, and &lt;strong&gt;dependency management&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's not just about syntax; it's about business impact and scalability. Are you ready to see the cards on the table? 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  🏎️ Lazy Loading: Who Loads Faster and Better?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lazy loading&lt;/strong&gt; is our ace up the sleeve for lightning-fast startup times. But how do these two giants implement it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular: The Master of JavaScript Modularity 🎯
&lt;/h3&gt;

&lt;p&gt;Angular, with its router, makes lazy loading a breeze. Loading entire &lt;strong&gt;feature modules&lt;/strong&gt; or individual &lt;strong&gt;standalone components&lt;/strong&gt; (thanks Angular 17!) only when needed is a dream for modularization.&lt;/p&gt;

&lt;p&gt;The mechanism is clean: it leverages dynamic ECMAScript &lt;code&gt;import()&lt;/code&gt;. The browser downloads only what's necessary, when it's necessary.&lt;br&gt;
Configuration? A single line in the routing:&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;// app-routing.module.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="nx"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&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;./admin/admin.module&lt;/span&gt;&lt;span class="dl"&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;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AdminModule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="nx"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&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;./dashboard/dashboard.component&lt;/span&gt;&lt;span class="dl"&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;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DashboardComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Angular 17+&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&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;A super-efficient flow that I love.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blazor WebAssembly: Loading DLLs? A New C# Frontier 🌐
&lt;/h3&gt;

&lt;p&gt;Blazor WebAssembly (.NET 5+) brought lazy loading to the .NET world, but the approach is different: we're talking about deferred loading of &lt;strong&gt;assemblies (.dll)&lt;/strong&gt;. Here, I had to reset my mind from the JavaScript paradigm.&lt;/p&gt;

&lt;p&gt;You declare the assembly in the &lt;code&gt;.csproj&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&amp;nbsp; &lt;span class="nt"&gt;&amp;lt;BlazorWebAssemblyLazyLoad&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"MyApp.Features.Admin.dll"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you manage it programmatically with &lt;code&gt;ILazyAssemblyLoader&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// MyComponent.razor.cs (example)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;ILazyAssemblyLoader&lt;/span&gt; &lt;span class="n"&gt;LazyAssemblyLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;LoadAdminFeature&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;assemblies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;LazyAssemblyLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadAssembliesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"MyApp.Features.Admin.dll"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="c1"&gt;// ... post-load logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, yes, but it requires more manual management at the project level.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚨 The Real Differences in Lazy Loading:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Characteristic&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Angular&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Blazor WebAssembly&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Loading Unit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Module/Chunk (.js), Standalone Component&lt;/td&gt;
&lt;td&gt;Assembly (.dll)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Granularity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (module or component)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Limited (assembly level)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Natively in the Router&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;.csproj configuration and C# code&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tooling &amp;amp; DX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mature (CLI, automatic)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Requires attention to project structure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;My take here?&lt;/strong&gt; Angular offers an almost magical fluidity in lazy loading thanks to its router integration. Blazor, while powerful, asks you to think more "in terms of .dll files" and less "in terms of UI routes."&lt;/p&gt;




&lt;h2&gt;
  
  
  🌳 Tree Shaking &amp;amp; Dead Code Elimination: Bundle Sizes Under Control?
&lt;/h2&gt;

&lt;p&gt;Optimizing bundle size is vital. Let's see who wins the "dead code" battle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular: The Scalpel of JS/TS Tree Shaking ✂️
&lt;/h3&gt;

&lt;p&gt;Angular, with Webpack/esbuild/Vite, is a master of &lt;strong&gt;tree shaking&lt;/strong&gt;. It removes unused JavaScript/TypeScript code thanks to static analysis of ES modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Leaner bundles, reduced loading times. It's a proven mechanism that works great with well-structured code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blazor WebAssembly: The .NET IL Linker – A Double-Edged Sword 🔪
&lt;/h3&gt;

&lt;p&gt;Blazor WebAssembly relies on the .NET &lt;strong&gt;IL linker&lt;/strong&gt; for "trimming." It analyzes the &lt;strong&gt;CIL bytecode&lt;/strong&gt; to eliminate unreachable classes and methods.&lt;/p&gt;

&lt;p&gt;Enable in &lt;code&gt;.csproj&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&amp;nbsp; &lt;span class="nt"&gt;&amp;lt;PublishTrimmed&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/PublishTrimmed&amp;gt;&lt;/span&gt;
&amp;nbsp; &lt;span class="nt"&gt;&amp;lt;TrimMode&amp;gt;&lt;/span&gt;link&lt;span class="nt"&gt;&amp;lt;/TrimMode&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;But beware!&lt;/strong&gt; Here I broke a sweat:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reflection:&lt;/strong&gt; Extensive use of reflection can fool the linker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untrimmed Libraries:&lt;/strong&gt; Not all .NET libraries are "trim-friendly."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warnings:&lt;/strong&gt; Monitoring linker warnings is &lt;strong&gt;FUNDAMENTAL&lt;/strong&gt; to avoid runtime issues. I've seen more than one &lt;code&gt;MissingMethodException&lt;/code&gt;!&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  ⚠️ The Insidious Differences in Tree Shaking:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Angular&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Blazor WebAssembly&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Basic Technique&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Static JS/TS analysis&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;IL Linker (CIL bytecode)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Target Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JavaScript&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;.NET Intermediate Language (CIL)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Effectiveness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Excellent with ES modules and pure functions&lt;/td&gt;
&lt;td&gt;Good, but compromised by reflection and untrimmed libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Main Risks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimal&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Runtime exceptions if linker is too aggressive&amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;My biggest challenge here?&lt;/strong&gt; Learning to "talk" to the IL linker. It's not an automatic process like JS tree shaking; it requires greater architectural awareness of dependencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ AOT vs JIT Compilation: Startup and Performance Under the Magnifying Glass
&lt;/h2&gt;

&lt;p&gt;Compilation is the heart of performance. Here, the two frameworks take radically different paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular: AOT by Default – JavaScript Ready to Go! 🚀
&lt;/h3&gt;

&lt;p&gt;Angular compiles &lt;strong&gt;AOT (Ahead-of-Time)&lt;/strong&gt; by default for production builds. HTML and TypeScript become optimized JavaScript &lt;em&gt;before&lt;/em&gt; the browser sees a single byte of code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits (which I love):&lt;/strong&gt; Fast startup, build-time errors (not runtime!), security, and better tree shaking. The browser downloads code that's already ready for execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blazor WebAssembly: JIT by Default, Optional AOT – A Difficult Compromise 🤔
&lt;/h3&gt;

&lt;p&gt;Blazor WebAssembly by default uses &lt;strong&gt;JIT (Just-in-Time)&lt;/strong&gt; compilation: the .NET runtime interprets the CIL code in the browser. This can slow down the initial startup.&lt;/p&gt;

&lt;p&gt;But from .NET 7+, there's &lt;strong&gt;optional AOT&lt;/strong&gt;: CIL is translated into &lt;strong&gt;native WebAssembly&lt;/strong&gt; before deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable AOT:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&amp;nbsp; &lt;span class="nt"&gt;&amp;lt;RunAOTCompilation&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/RunAOTCompilation&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Blazor AOT Advantages:&lt;/strong&gt; Incredible runtime performance, less memory consumption.&lt;br&gt;
&lt;strong&gt;Blazor AOT Disadvantages (and this is where the choice becomes critical):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MASSIVE Payload Increase:&lt;/strong&gt; Compiled WebAssembly code is &lt;em&gt;much&lt;/em&gt; larger. This is a trade-off you must justify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Very Long Build Times:&lt;/strong&gt; AOT compilation is intensive and can extend build times.&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  🤯 Crucial Differences in Compilation:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Angular (AOT)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Blazor WASM (JIT Default)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Blazor WASM (Optional AOT)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Phase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build-time&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Runtime&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Build-time&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Slow (downloads .NET runtime + CIL interpreter)&lt;/td&gt;
&lt;td&gt;Medium (downloads larger native WebAssembly)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Good (optimized JS)&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Medium (CIL interpretation)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;High (native WebAssembly)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payload Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optimized&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Medium (.NET Runtime + CIL)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Very heavy&lt;/strong&gt; (.NET Runtime + WASM)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The architect's dilemma?&lt;/strong&gt; In Blazor, you either accept a slower startup with a contained payload (JIT), or gain runtime speed by heavily sacrificing bundle size (AOT). Angular, on the other hand, offers you the best of both worlds for JavaScript frontend.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧬 Shared Dependency Management: The Double-Edged Sword of Modularity
&lt;/h2&gt;

&lt;p&gt;How do we manage common code? Both have an approach, but with different consequences.&lt;/p&gt;
&lt;h3&gt;
  
  
  Angular: Injector Hierarchy and &lt;code&gt;providedIn: 'root'&lt;/code&gt; – The Singleton Paradigm 🌿
&lt;/h3&gt;

&lt;p&gt;In Angular, &lt;code&gt;SharedModule&lt;/code&gt;s are the norm. The key is the &lt;strong&gt;injector hierarchy&lt;/strong&gt;. I've learned that for global, tree-shakable singleton services, &lt;code&gt;providedIn: 'root'&lt;/code&gt; is a blessing:&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;// my-singleton.service.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Singleton and tree-shakable&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;class&lt;/span&gt; &lt;span class="nc"&gt;MySingletonService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, you risk multiple instances of services in lazy-loaded modules, a classic mistake to avoid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blazor WebAssembly: Shared Projects and Linker Constraints – .NET in the Browser 🏗️
&lt;/h3&gt;

&lt;p&gt;Blazor uses .NET class libraries (&lt;code&gt;.dll&lt;/code&gt;) for code sharing (&lt;code&gt;MyApp.Shared&lt;/code&gt;). It seems familiar to .NET developers, but...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beware of the pitfalls!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payload:&lt;/strong&gt; Even a small part of a DLL can drag the entire assembly into the bundle, unless the linker works miracles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incompatible Dependencies:&lt;/strong&gt; I've spent hours debugging &lt;code&gt;System.IO.File&lt;/code&gt; or Entity Framework Core magically appearing in the browser and crashing everything! Server-side APIs don't live well in WebAssembly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trimming:&lt;/strong&gt; Shared libraries must be &lt;em&gt;trim-friendly&lt;/em&gt; or the linker won't do its job.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Best Practices and Architectural Considerations: My Lessons from the Field
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Angular:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Services:&lt;/strong&gt; Always &lt;code&gt;providedIn: 'root'&lt;/code&gt; for singletons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularity:&lt;/strong&gt; Logical, lazy-loaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle Analysis:&lt;/strong&gt; Webpack Bundle Analyzer is my best friend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI Performance:&lt;/strong&gt; &lt;code&gt;OnPush&lt;/code&gt; change detection where possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Blazor WebAssembly:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared Projects:&lt;/strong&gt; Minimal and lightweight, only DTOs and strictly client-side logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate Dependencies:&lt;/strong&gt; No server-centric dependencies on the client!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linker:&lt;/strong&gt; Configure it aggressively and monitor IL warnings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflection:&lt;/strong&gt; Use it extremely sparingly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AOT:&lt;/strong&gt; Evaluate it only for critical performance needs, aware of the payload increase.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion of the "Duel" 🥊
&lt;/h2&gt;

&lt;p&gt;Angular and Blazor WebAssembly are both thoroughbreds for client-side development, but with different souls.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Angular:&lt;/strong&gt; Maturity, refined integration of lazy loading and tree shaking, solid ground for those seeking robustness and a vast ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blazor WebAssembly:&lt;/strong&gt; The excitement of bringing C# to the browser. A flat learning curve for .NET devs, but requires constant vigilance on dependency management and runtime/compilation implications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;As an architect, my choice is never "one or the other" upfront.&lt;/strong&gt; It depends on project requirements, team expertise, and tolerance for compromises.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤯 Final Thoughts from an Architect: When I Wouldn't Use...
&lt;/h2&gt;

&lt;p&gt;This is the most important part for me: &lt;strong&gt;when is a framework not the best solution?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  I wouldn't use Blazor WebAssembly if...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Absolute priority on initial load and mini bundles:&lt;/strong&gt; The .NET runtime, while optimized, can be an obstacle for sites requiring an almost instantaneous "first paint."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum runtime performance with high computational loads (without AOT):&lt;/strong&gt; If I cannot accept the AOT payload increase, default CIL interpretation might not be enough for intensive calculations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extended frontend ecosystem and mature UI libraries:&lt;/strong&gt; For very specific UI/UX needs, the Blazor ecosystem, while growing, is still less supplied than the JavaScript one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  I wouldn't use Angular if...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small, rapid projects or prototypes:&lt;/strong&gt; Its opinionated structure and "boilerplate" can slow down contexts where prototyping speed is the absolute priority. Angular can be "overkill."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team with little frontend experience or preference for other paradigms:&lt;/strong&gt; The learning curve for TypeScript, RxJS, and complex DI can be steep. If the team prefers more minimalist libraries, Angular might not be the right choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum end-to-end C# code reuse:&lt;/strong&gt; If the goal is to reuse C# business logic between backend and frontend, Angular obviously doesn't offer this intrinsic advantage.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;And you? What have been your experiences or your "never agains" with Angular or Blazor WebAssembly? I'm curious to hear your perspectives! 👇&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🔗 Useful Resources (For those who want to dig deeper!):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://angular.io/guide/lazy-loading-modules" rel="noopener noreferrer"&gt;Angular - Lazy-loading feature modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/webassembly-lazy-load-assemblies?view=aspnetcore-8.0" rel="noopener noreferrer"&gt;Microsoft Docs - Lazy load assemblies in Blazor WebAssembly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=https://angular.io/guide/build%23tree-shaking" rel="noopener noreferrer"&gt;Angular - Tree shaking guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=https://learn.microsoft.com/en-us/dotnet/core/deploying/trim-self-contained%3Fpivots%3Ddotnet-8-0" rel="noopener noreferrer"&gt;Microsoft Docs - Trim self-contained deployments and executables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://angular.io/guide/aot-compiler" rel="noopener noreferrer"&gt;Angular - Ahead-of-Time (AOT) compilation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=https://learn.microsoft.com/en-us/aspnet/core/blazor/webassembly-aot-compilation%3Fview%3Daspnetcore-8.0" rel="noopener noreferrer"&gt;Microsoft Docs - ASP.NET Core Blazor WebAssembly AOT compilation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>balzor</category>
      <category>angular</category>
      <category>dotnet</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
