Cover Image for Exploring micro-frontends: why, when & how (and why I'm doing it anyway)

Exploring micro-frontends: why, when & how (and why I'm doing it anyway)

3 min read

“A path is whatever you make of it—as long as it gets you where you want to go.”Carl Eubanks, 2025


Step back and see the big picture

I always start with why. Call it a ritual: three deep breaths, zoom the camera out, and ask whether the work in front of me deserves the next chunk of my life. The answer here is yes—because slicing a monolith UI into purposeful micro‑frontends (MFEs) is a force‑multiplier for experimentation, autonomy, and runtime performance.

State of averageprogrammer today

AverageProgrammer.com is a single Next 13 monorepo:

apps/
  web/           → routes, pages, components
packages/
  ui/            → shared design‑system components
  tsconfig/
  eslint‑config/

Routes in play:

PathPurpose
/Landing page / app‑shell
/aboutWho‑am‑I résumé
/blogMarkdown + MDX blog
/playgroundSandboxes, demos, and half‑baked ideas

There is nothing wrong with this. Monorepos rock—until your build times, ownership boundaries, or deployment frequency say otherwise.

What could become a micro-frontend?

CandidateRationaleTeam Boundary?
BlogRich MDX rendering, static cacheable content, could deploy on its own cadenceContent team (me)
AboutInfreq‑changed; could lazy‑loadNone, maybe keep inline
PlaygroundHeavy experiment code, extra depsR&D playground
Shell/CoreApp‑chrome, header, auth contextPlatform

Approach options (pick your poison)

A. Module Federation

Webpack 5 ↔ Vite 4 (via vite-plugin-federation) lets each build expose and consume modules at runtime.

Metaphor time. Four strongholds defend your kingdom. Wyverns suddenly love the blog stronghold. You station elite super‑humans in an abandoned village, ready to reinforce whichever wall starts smoking. That’s dynamic import & shared scope in Module Federation—push code where it’s needed, when it’s needed.

Pros → No iframes, single runtime, shared React context.
Cons → Webpack config tax, runtime coupling, version skew danger.

B. Isolated MFEs (iframe / web components)

Pure isolation at the cost of double bundles and cross‑window messaging.
Not my jam for personal projects with a tiny team of 👤 = 1.

C. Keep It monolithic until proven painful

Baseline. Perfectly acceptable.

Why I tried out module federation

  • Incremental adoption – each route splits out when ROI > 0.
  • Single hosting bill – still fits Vercel Free Tier by using path‑based rewrites.
  • Shared Tailwind design tokens travel via @ui package ‑> no CSS duplication.

Vercel setup - path-based routing

{
  "rewrites": [
    { "source": "/blog/:path*", "destination": "https://blog.averageprogrammer.com/:path*" },
    { "source": "/playground/:path*", "destination": "https://play.averageprogrammer.com/:path*" }
  ],
  "redirects": [
    { "source": "/rss.xml", "destination": "https://blog.averageprogrammer.com/rss.xml", "permanent": true }
  ]
}

Each MFE is built once, deployed to its own Vercel project/domain, and the shell transparently rewrites.

Tip 🔧: Add Cache‑Control: public, max‑age=31536000, immutable headers on the blog static assets. Free CDN FTW.

Gotchas & guardrails

RiskMitigation
Version drift between host & remoteLock shared versions, enable eager: true for critical deps
Bundle bloatKeep MFEs lean; everything shared lives in ui package
Hydration mismatchPin React version, test per‑MFE preview URLs

What’s next?

  1. Extract /blog → separate Vite + MDX build, federate <BlogPost /> list component back to shell.
  2. CI Matrix – per‑MFE lint + typecheck to stop breaking shared contracts.
  3. Edge Middleware – AB test old vs. new Blog without harming SEO.


Conclusion

Micro‑frontends aren’t magic—they’re just another slice on the “right tool, right job” cake. For averageprogrammer.com, Module Federation hits the sweet spot between autonomy and zero‑dollar hosting. If tomorrow’s metrics say otherwise, great—I’ll swap the knife for a different utensil. That’s the whole point: architecture is fluid, just like code.

💡
I’m glad you got to this point. I want to callout that I did in fact go through with this, but then rolled back. It was a test to see what it would take. Otherwise, averageprogrammer isn’t there yet to need to do this—although, it was quite nicely organized.