<?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>Forem: Fabio Arcari</title>
    <description>The latest articles on Forem by Fabio Arcari (@arcari).</description>
    <link>https://forem.com/arcari</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3496651%2F3ce1a95d-2088-40bb-b218-d04d5d457ffd.jpeg</url>
      <title>Forem: Fabio Arcari</title>
      <link>https://forem.com/arcari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/arcari"/>
    <language>en</language>
    <item>
      <title>The Tiny Proxy That Fixed Local Development for Our Multi-Repo Frontend</title>
      <dc:creator>Fabio Arcari</dc:creator>
      <pubDate>Thu, 14 May 2026 09:43:02 +0000</pubDate>
      <link>https://forem.com/subito/the-tiny-proxy-that-fixed-local-development-for-our-multi-repo-frontend-518b</link>
      <guid>https://forem.com/subito/the-tiny-proxy-that-fixed-local-development-for-our-multi-repo-frontend-518b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is a follow-up to &lt;a href="https://dev.to/subito/from-independent-microsites-to-context-driven-architecture-5166"&gt;From Independent Microsites to Context-Driven Architecture&lt;/a&gt;, where we explain why we split our frontend into multiple independent Next.js repositories.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you'd rather explore the code before reading the architecture behind it, a small demo is available here: &lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini" rel="noopener noreferrer"&gt;https://github.com/Subito-it/articles-code/tree/main/olympus-mini&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Everyone in a Micro-Frontend Setup Hits
&lt;/h2&gt;

&lt;p&gt;Your production environment has a smart edge router: Akamai, Cloudflare, nginx, whatever, that maps URL paths to different frontend applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/search         → next.js app A
/ads            → next.js app B
/profile        → next.js app C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works beautifully in production. Then you open your laptop.&lt;/p&gt;

&lt;p&gt;Each app runs on its own port, and suddenly you're managing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost:3001  → app A
localhost:3002  → app B
localhost:3003  → app C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things break immediately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-app links stop working.&lt;/strong&gt; If app A renders &lt;code&gt;&amp;lt;Link href="/ads/123"&amp;gt;&lt;/code&gt;, that link resolves relative to &lt;code&gt;localhost:3001&lt;/code&gt;; wrong app, wrong port. You either hardcode ports in your local env vars, or you just accept that links are broken locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting everything is a chore.&lt;/strong&gt; Feature work that spans two apps means two terminals, two &lt;code&gt;npm run dev&lt;/code&gt; commands, and memorizing which port does what. Add a third app and it gets worse.&lt;/p&gt;

&lt;p&gt;You can't replicate your edge infrastructure locally. It's a cloud service. But you don't need to. You need something much smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea: A Purpose-Built Local Proxy
&lt;/h2&gt;

&lt;p&gt;Instead of reaching for Docker Compose + nginx (which works, but is heavy and requires maintaining config that mirrors production), we built a small tool called &lt;strong&gt;Olympus&lt;/strong&gt;: a Node.js HTTPS reverse proxy paired with a bash orchestration script.&lt;/p&gt;

&lt;p&gt;It does the following things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path-based routing&lt;/strong&gt;: all apps are reachable under one local domain, with each URL path forwarded to the correct app, just like a monolith.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API proxying without CORS&lt;/strong&gt;: apps call a local &lt;code&gt;/api-proxy&lt;/code&gt; route instead of hitting the staging API directly. The proxy forwards those requests, so the app never makes a cross-origin request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API mocking&lt;/strong&gt;: a flag swaps the staging API for a local &lt;a href="https://mockoon.com/" rel="noopener noreferrer"&gt;Mockoon&lt;/a&gt; instance, with no changes to app code required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single command&lt;/strong&gt;: a global command starts one or more apps together, with unified log output.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Path-based routing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setting Up the Local Domain
&lt;/h3&gt;

&lt;p&gt;The foundation is a fake local domain shared across the team, made possible by adding one entry to the &lt;code&gt;/etc/hosts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;  &lt;span class="n"&gt;www&lt;/span&gt;.&lt;span class="n"&gt;myapp&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;https://www.myapp.local:9443&lt;/code&gt; resolves to your machine. The proxy listens there and routes from a single origin, the same shape as production.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Routing Works
&lt;/h3&gt;

&lt;p&gt;The proxy is a Node.js HTTPS server using &lt;a href="https://github.com/http-party/node-http-proxy" rel="noopener noreferrer"&gt;&lt;code&gt;http-proxy&lt;/code&gt;&lt;/a&gt;. Each app runs on a fixed local port, defined once in config (&lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini/proxy/config.js" rel="noopener noreferrer"&gt;&lt;code&gt;proxy/config.js&lt;/code&gt;&lt;/a&gt;):&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-ads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3003&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;Routing is a list of regex patterns matched in order, first-match-wins, using the same mental model as nginx &lt;code&gt;location&lt;/code&gt; blocks (&lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini/proxy/utils/routing.js" rel="noopener noreferrer"&gt;&lt;code&gt;proxy/utils/routing.js&lt;/code&gt;&lt;/a&gt;):&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;// www.myapp.local&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;ads/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-ads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;profile/&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-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="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// catch-all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a request arrives, the proxy matches the path, looks up the port, and forwards it.&lt;br&gt;
The Next.js app receives the request as if it came directly; it has no idea it's behind a proxy.&lt;/p&gt;
&lt;h2&gt;
  
  
  API proxying without CORS
&lt;/h2&gt;

&lt;p&gt;Our apps in staging hit APIs on a different domain (e.g. &lt;code&gt;api.staging.subito.it&lt;/code&gt;). Locally, the browser origin is &lt;code&gt;www.myapp.local&lt;/code&gt;, so any direct call to that API would be cross-origin and blocked by the browser unless the server responds with the right CORS headers, which we don't control on the staging API.&lt;/p&gt;

&lt;p&gt;The fix is to never make that cross-origin request at all. In the local environment, each app points its API base URL at a &lt;code&gt;/api-proxy/*&lt;/code&gt; route on the same origin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://www.myapp.local:9443/api-proxy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy intercepts requests matching &lt;code&gt;/api-proxy/*&lt;/code&gt;, strips the prefix, and forwards them server-side to the real staging API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Browser
                    ↓
https://www.myapp.local/api-proxy/users/list
                    ↓
 https://api.staging.subito.it/users/list 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the browser's perspective the request never leaves &lt;code&gt;www.myapp.local&lt;/code&gt;, so CORS is never triggered.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Mocking with Mockoon
&lt;/h2&gt;

&lt;p&gt;A specific flag swaps the staging API for a local &lt;a href="https://mockoon.com/" rel="noopener noreferrer"&gt;Mockoon&lt;/a&gt; instance, with no changes to app code required.&lt;/p&gt;

&lt;p&gt;When you pass the flag, the Mockoon CLI starts and the proxy is launched, which redirects all &lt;code&gt;/api-proxy&lt;/code&gt; traffic from the real staging API to Mockoon running on &lt;code&gt;localhost:9999&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Browser
                    ↓
https://www.myapp.local/api-proxy/users/list
                    ↓
 http://localhost:9999/users/list (Mockoon)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because mock definitions live in the repo, every developer gets the same responses without any manual setup. The script also watches the JSON file for changes and restarts Mockoon automatically, so updating a mock response takes effect immediately.&lt;/p&gt;

&lt;p&gt;This is useful when staging is unstable, you're offline, or you need a deterministic response for a scenario that's hard to reproduce against a real API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Single Command
&lt;/h2&gt;

&lt;p&gt;The orchestration lives in a bash script (&lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini/start.sh" rel="noopener noreferrer"&gt;&lt;code&gt;start.sh&lt;/code&gt;&lt;/a&gt;), invoked via a short wrapper (&lt;code&gt;o&lt;/code&gt;) added to your &lt;code&gt;$PATH&lt;/code&gt; during setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;o app-search                        &lt;span class="c"&gt;# proxy + one app&lt;/span&gt;
o app-search app-ads app-profile    &lt;span class="c"&gt;# proxy + three apps&lt;/span&gt;
o &lt;span class="nt"&gt;--mock&lt;/span&gt; app-search                 &lt;span class="c"&gt;# route API calls to local mocks&lt;/span&gt;
o &lt;span class="nt"&gt;--verbose&lt;/span&gt; app-search              &lt;span class="c"&gt;# detailed proxy request logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Any existing proxy process is killed (tracked via PID file)&lt;/li&gt;
&lt;li&gt;Any process already occupying each app's port is killed&lt;/li&gt;
&lt;li&gt;The HTTPS proxy starts&lt;/li&gt;
&lt;li&gt;For each app: env vars are synced, then &lt;code&gt;npm run dev&lt;/code&gt; starts&lt;/li&gt;
&lt;li&gt;All apps run in parallel, each with a colored &lt;code&gt;[app-name]&lt;/code&gt; prefix in the shared log stream&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;Ctrl+C&lt;/code&gt; kills everything cleanly via a &lt;code&gt;SIGTERM&lt;/code&gt; trap.&lt;/p&gt;

&lt;p&gt;The colored output is a small thing that makes a big difference — when five apps are logging simultaneously, being able to instantly tell which one produced a line saves real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Docker + nginx?
&lt;/h2&gt;

&lt;p&gt;We considered it. The honest answer: it's more than we needed, and the maintenance cost compounds.&lt;/p&gt;

&lt;p&gt;With Docker + nginx you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An nginx config that needs to mirror your routing rules&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;docker-compose.yml&lt;/code&gt; to keep in sync with app changes&lt;/li&gt;
&lt;li&gt;Slower startup&lt;/li&gt;
&lt;li&gt;One more thing that works differently across developer machines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a Node.js proxy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Routing config is one JS file&lt;/li&gt;
&lt;li&gt;Startup is instant&lt;/li&gt;
&lt;li&gt;It's just a process, &lt;code&gt;kill&lt;/code&gt;, &lt;code&gt;restart&lt;/code&gt;, done&lt;/li&gt;
&lt;li&gt;Any frontend dev can read and modify it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle: &lt;strong&gt;a small purpose-built tool beats a general one when the problem is well-scoped.&lt;/strong&gt; Local dev routing is a well-scoped problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If your team runs multiple frontend apps locally and you're dealing with port juggling and broken cross-app links, the pattern is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a shared local domain, add it to &lt;code&gt;/etc/hosts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Write a small HTTPS proxy with path-based routing&lt;/li&gt;
&lt;li&gt;Wrap the startup logic in a single command&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need to replicate your production edge infrastructure. You need something that behaves enough like it to make local development feel natural. That bar is much lower than it looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;A minimal working demo, three apps, one proxy, one command, is available at &lt;strong&gt;&lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini" rel="noopener noreferrer"&gt;https://github.com/Subito-it/articles-code/tree/main/olympus-mini&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The key files:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini/proxy/config.js" rel="noopener noreferrer"&gt;&lt;code&gt;proxy/config.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Port mappings and SSL paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini/proxy/utils/routing.js" rel="noopener noreferrer"&gt;&lt;code&gt;proxy/utils/routing.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Regex rules → app mapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini/proxy/server.js" rel="noopener noreferrer"&gt;&lt;code&gt;proxy/server.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;HTTPS server + http-proxy wiring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Subito-it/articles-code/tree/main/olympus-mini/start.sh" rel="noopener noreferrer"&gt;&lt;code&gt;start.sh&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Kill ports, start proxy + apps in parallel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No framework dependencies. &lt;/p&gt;

</description>
      <category>frontend</category>
      <category>microservices</category>
      <category>node</category>
      <category>devex</category>
    </item>
    <item>
      <title>From Microsites to Context-Driven Architecture: Lessons Learned</title>
      <dc:creator>Fabio Arcari</dc:creator>
      <pubDate>Fri, 05 Sep 2025 21:15:27 +0000</pubDate>
      <link>https://forem.com/subito/from-independent-microsites-to-context-driven-architecture-5166</link>
      <guid>https://forem.com/subito/from-independent-microsites-to-context-driven-architecture-5166</guid>
      <description>&lt;p&gt;&lt;em&gt;From 12 separate microsites to 6 context-specific ones: the web architecture powering &lt;a href="https://www.subito.it/" rel="noopener noreferrer"&gt;subito.it&lt;/a&gt;, Italy’s leading online classifieds platform, and the lessons we learned from overengineering.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Four years ago, at &lt;a href="https://www.subito.it/" rel="noopener noreferrer"&gt;subito.it&lt;/a&gt;, we had fully embraced the microsite approach. &lt;br&gt;
Every page or small group of pages had its own repository, its own deploy pipeline, and its own infrastructure. &lt;br&gt;
It was the era of the article &lt;a href="https://adevinta.com/techblog/our-microsite-architecture/" rel="noopener noreferrer"&gt;"Our microsite architecture"&lt;/a&gt; published on Adevinta's tech blog, where we proudly described how we had "accelerated the frequency of releases and the independence of teams by applying a microsite approach."&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a Microsite?
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;microsite&lt;/strong&gt; is an architectural approach where individual pages or small groups of related pages are developed, deployed, and maintained as completely separate applications. &lt;br&gt;
Each microsite typically has its own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository and codebase&lt;/li&gt;
&lt;li&gt;CI/CD pipeline and deployment process&lt;/li&gt;
&lt;li&gt;Infrastructure and hosting environment&lt;/li&gt;
&lt;li&gt;Technology stack (which can differ from other microsites)&lt;/li&gt;
&lt;li&gt;Team ownership and responsibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach contrasts with traditional monolithic applications where all pages are part of a single large codebase. &lt;br&gt;
Microsites aim to provide maximum team autonomy and deployment independence, allowing different teams to work on different parts of a website without interfering with each other.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Today, in 2025, we have an update on this architecture: we've found a better balance between agility and maintainability through continuous learning and optimization&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Microsite Era: When Everything Seemed Perfect
&lt;/h2&gt;

&lt;p&gt;The microsite approach we had adopted seemed like the ideal solution for our problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lean pipelines&lt;/strong&gt;: each microsite had a fast and independent deploy pipeline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team autonomy&lt;/strong&gt;: each team could work on their own piece without interference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent releases&lt;/strong&gt;: no mutual blocking between teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over time, however, we started noticing the first issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Uncontrolled Repository Proliferation&lt;/strong&gt;, what had started as a strategy for important pages had begun to be applied to secondary pages as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed Technical Debt&lt;/strong&gt;, with 12 repositories to maintain, some pages started falling behind in dependency updates and security patches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excessive Infrastructure Overhead&lt;/strong&gt;, each microsite required:

&lt;ul&gt;
&lt;li&gt;Monitoring configuration&lt;/li&gt;
&lt;li&gt;Alerting setup&lt;/li&gt;
&lt;li&gt;Pipeline maintenance&lt;/li&gt;
&lt;li&gt;CDN Configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The New Strategy: Context-Driven Microsites
&lt;/h2&gt;

&lt;p&gt;Instead of going completely back to a monolith (which would have reintroduced the problem of overly long pipelines), we chose a middle path: &lt;strong&gt;context-specific microsites&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Principles of the New Architecture
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context First&lt;/strong&gt;: we group pages that share the same business domain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear Ownership&lt;/strong&gt;: each microsite remains owned by a specific team&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manageable Size&lt;/strong&gt;: large enough to justify the infrastructure, small enough to remain maintainable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our new architecture consolidates related pages into context-specific groupings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Public Content Hub&lt;/strong&gt;: All public content pages and SEO-relevant content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ad Insertion Flow&lt;/strong&gt;: The entire ad insertion workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication Service&lt;/strong&gt;: Authentication, login and sign-up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction Service&lt;/strong&gt;: The entire transaction ecosystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid Options Service&lt;/strong&gt;: Paid services and premium options&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Management Service&lt;/strong&gt;: Personal area and profile management&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Transition Process
&lt;/h2&gt;

&lt;p&gt;Our migration follows a structured approach with three main phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Preparation and Setup&lt;/strong&gt;&lt;br&gt;
The initial phase focuses on preparing the target microsite to receive the migrated functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up dedicated layouts and components in the target architecture&lt;/li&gt;
&lt;li&gt;Import and adapt components to the consolidated codebase&lt;/li&gt;
&lt;li&gt;Update environment variables and configuration management&lt;/li&gt;
&lt;li&gt;Establish monitoring and alerting for the new consolidated service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Quality Assurance and Testing&lt;/strong&gt;&lt;br&gt;
Before going live, we conduct comprehensive checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute automated end-to-end (E2E) and manual tests&lt;/li&gt;
&lt;li&gt;Validate third-party integrations (e.g., GTM)&lt;/li&gt;
&lt;li&gt;Verify page headers, caching policies, and security configurations&lt;/li&gt;
&lt;li&gt;Confirm monitoring and metrics collection are correctly implemented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Production Migration and Cleanup&lt;/strong&gt;&lt;br&gt;
The final phase involves the actual cutover and decommissioning of legacy systems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gradual Rollout:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route traffic progressively from legacy to consolidated microsite&lt;/li&gt;
&lt;li&gt;Monitor performance and error rates during the transition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Legacy Cleanup:&lt;/strong&gt;&lt;br&gt;
Following our established Service End-of-Life procedures, we systematically decommission the old microsite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Destroy monitoring, alerts, and dashboards&lt;/li&gt;
&lt;li&gt;Archive repositories and remove deployment configurations&lt;/li&gt;
&lt;li&gt;Clean up infrastructure resources (databases, IAM roles, etc.)&lt;/li&gt;
&lt;li&gt;Update routing and add redirects where necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Our journey from microsites to a more consolidated approach wasn't a failure of microsite architecture, but rather a maturation of our understanding of when and how to apply it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architecture must evolve with business and team needs&lt;/li&gt;
&lt;li&gt;Not everything needs to be a microservice/microsite - like any architectural pattern, it has its trade-offs&lt;/li&gt;
&lt;li&gt;Context is fundamental, group by business context, not technical convenience&lt;/li&gt;
&lt;li&gt;Pages that belong to the same user flow should probably stay together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, with our 6 context-driven microsites, we've found a balance that allows us to maintain team autonomy and deployment speed while significantly reducing maintenance overhead.&lt;/p&gt;

&lt;p&gt;The path to perfect architecture doesn't exist, but the path to ever-improving architecture does.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
