micro-frontend Svelte Themes

Micro Frontend

Micro frontend PoC with Turborepo, Next.js Multi-Zones shell, React, Svelte, Vue, and Angular

Micro Frontend PoC

A proof of concept demonstrating a micro frontend architecture using Turborepo as the monorepo orchestrator and Next.js Multi-Zones as the shell pattern. Five independent micro frontends — each built with a different framework — are composed under a single entry point. A shared Web Component (<app-nav>) provides consistent navigation across all zones with zero build-time coupling.

Table of Contents


Architecture Overview

                         ┌─────────────────────┐
                         │   Browser Request    │
                         │  http://localhost:3000│
                         └──────────┬───────────┘
                                    │
                         ┌──────────▼───────────┐
                         │       Shell          │
                         │   (Next.js :3000)    │
                         │                      │
                         │  - Rewrite hub       │
                         │  - Home page         │
                         │  - NO nav rendering  │
                         └──────────┬───────────┘
                                    │
              ┌─────────────────────┼─────────────────────┐
              │                     │                     │
     ┌────────▼────────┐  ┌────────▼────────┐  ┌────────▼────────┐
     │ beforeFiles      │  │ fallback         │  │ fallback         │
     │ rewrites         │  │ rewrites         │  │ rewrites         │
     └────────┬────────┘  └────────┬────────┘  └────────┬────────┘
              │                     │                     │
    ┌─────────▼──────────┐         │                     │
    │  Next.js App       │         │                     │
    │  /nextapp → :3001  │         │                     │
    │  (Multi-Zone)      │         │                     │
    └────────────────────┘         │                     │
                          ┌────────┴────────────────────────┐
                          │         Vite / Angular          │
                          │         Dev Servers             │
                          ├────────────────────────────────┤
                          │  React    /reactapp  → :3002   │
                          │  Svelte   /svelteapp → :3003   │
                          │  Vue      /vueapp    → :3004   │
                          │  Angular  /angularapp→ :3005   │
                          └────────────────────────────────┘

    ┌──────────────────────────────────────────────────────┐
    │              Nav Component (:3006)                    │
    │     <app-nav> Web Component loaded by ALL zones       │
    │     via <script src="http://localhost:3006/...">      │
    │                                                      │
    │     Zero build-time coupling — each zone loads        │
    │     the nav at runtime, no npm dependency needed      │
    └──────────────────────────────────────────────────────┘

The shell acts as a rewrite hub in development. It does not render navigation — that responsibility belongs to each zone via the shared <app-nav> Web Component. All user traffic enters through localhost:3000, and the shell forwards requests to the appropriate micro frontend's dev server.

Navigation between micro frontends triggers a full-page browser navigation (vertical splitting). Each app owns its own JavaScript bundle, framework runtime, and application state.


Web Component Nav

The <app-nav> custom element is a framework-agnostic Web Component that provides consistent navigation across all zones. It is built with vanilla TypeScript and Vite, uses Shadow DOM for style isolation, and is loaded by each zone via a <script> tag at runtime.

How it works

  1. The nav-component app (apps/nav-component/) builds a single JavaScript file (nav.js)
  2. Each zone includes <script type="module" src="http://localhost:3006/src/nav.ts"> in its HTML
  3. Each zone renders <app-nav> with its own links and app-name attributes
  4. The Web Component registers itself via customElements.define("app-nav", AppNav)
  5. Shadow DOM encapsulates styles — no CSS leaks between the nav and zone content

Usage in any zone

<script type="module" src="http://localhost:3006/src/nav.ts"></script>
<app-nav
  app-name="Micro Frontend PoC"
  links='[{"label":"Home","href":"/"},{"label":"Next.js","href":"/nextapp"}]'
></app-nav>

Attributes

Attribute Type Description
app-name string Title displayed on the left side of the nav bar
links JSON Stringified array of { label, href } objects

Each zone defines its own links — this means adding a link to a zone's nav is a change inside that zone only, not the shared component.


Project Structure

micro-frontend/
├── apps/
│   ├── shell/              # Next.js rewrite hub (port 3000)
│   │   ├── src/app/
│   │   │   ├── layout.tsx  # Loads <app-nav> via <Script>, no inline nav
│   │   │   └── page.tsx    # Home page with framework cards
│   │   └── next.config.ts  # Rewrite rules for all micro frontends
│   │
│   ├── nav-component/      # Web Component nav (port 3006)
│   │   ├── src/
│   │   │   └── nav.ts      # <app-nav> custom element (Shadow DOM)
│   │   ├── index.html      # Dev harness for testing in isolation
│   │   └── vite.config.ts  # Lib mode build → nav.js
│   │
│   ├── next-app/           # Next.js micro frontend (port 3001)
│   │   ├── src/app/
│   │   │   ├── layout.tsx  # Loads <app-nav> via <Script>
│   │   │   └── page.tsx
│   │   └── next.config.ts  # basePath + assetPrefix config
│   │
│   ├── react-app/          # React + Vite micro frontend (port 3002)
│   │   ├── src/
│   │   │   ├── main.tsx
│   │   │   └── App.tsx
│   │   ├── index.html      # Loads <app-nav> in HTML
│   │   └── vite.config.ts
│   │
│   ├── svelte-app/         # SvelteKit micro frontend (port 3003)
│   │   ├── src/
│   │   │   ├── app.html    # Loads <app-nav> in HTML
│   │   │   └── routes/
│   │   │       └── +page.svelte
│   │   └── svelte.config.js
│   │
│   ├── vue-app/            # Vue + Vite micro frontend (port 3004)
│   │   ├── src/
│   │   │   ├── main.ts
│   │   │   └── App.vue
│   │   ├── index.html      # Loads <app-nav> in HTML
│   │   └── vite.config.ts
│   │
│   └── angular-app/        # Angular micro frontend (port 3005)
│       ├── src/
│       │   ├── index.html  # Loads <app-nav> in HTML
│       │   ├── main.ts
│       │   └── app/
│       │       ├── app.ts
│       │       └── app.html
│       └── angular.json
│
├── packages/
│   └── shared/             # Shared constants and types
│       └── src/
│           └── index.ts    # App registry, colors, types
│
├── turbo.json              # Turborepo task configuration
├── pnpm-workspace.yaml     # pnpm workspace definition
├── package.json            # Root scripts
└── README.md

Prerequisites

Tool Minimum Version Check Command
Node.js 18.x or later node -v
pnpm 9.x or later pnpm -v

If you don't have pnpm installed:

npm install -g pnpm

Or use Corepack (bundled with Node.js 16.9+):

corepack enable
corepack prepare pnpm@latest --activate

Quick Start

# 1. Clone the repository
git clone https://github.com/gAlexander77/micro-frontend.git
cd micro-frontend

# 2. Install all dependencies
pnpm install

# 3. Start the entire application (all 7 dev servers)
pnpm dev

Open http://localhost:3000 in your browser. You will see the shell home page with the Web Component nav bar and cards linking to each micro frontend.

The single pnpm dev command starts all seven dev servers in parallel via Turborepo:

  • Shell (:3000), Next.js App (:3001), React (:3002), Svelte (:3003), Vue (:3004), Angular (:3005), Nav Component (:3006)

Available Scripts

All scripts are run from the repository root.

Command Description
pnpm dev Start all 7 dev servers in parallel (shell + 5 zones + nav component)
pnpm build Build all apps for production
pnpm lint Run linters across all apps
pnpm clean Remove build artifacts from all apps

Running a single app

To start only one app (useful for focused development), use pnpm's filter:

# Start only the shell
pnpm --filter @micro-frontend/shell dev

# Start only the React micro frontend
pnpm --filter @micro-frontend/react-app dev

# Start the shell, nav component, and next-app together
pnpm --filter @micro-frontend/shell --filter @micro-frontend/nav-component --filter @micro-frontend/next-app dev

Note: The nav component must be running for any zone's navigation to render. When developing a single zone, start both the zone and the nav component.


Applications

Shell (Next.js) — Port 3000

The shell is the rewrite hub for the entire application. It provides:

  • Rewrite rules in next.config.ts that proxy requests to zone dev servers
  • A home page with styled cards for each framework
  • Loads the <app-nav> Web Component — it does not render navigation itself

The shell is intentionally minimal. Its only routing job is forwarding requests. Navigation is owned by the Web Component.

URL: http://localhost:3000


A vanilla TypeScript + Vite app that builds the <app-nav> custom element. This is the single source of truth for navigation chrome across all zones.

  • Uses Shadow DOM for complete style isolation
  • Accepts app-name and links attributes
  • Built as a library (vite build outputs a single nav.js file)
  • In dev, Vite serves the raw TypeScript at /src/nav.ts
  • CORS enabled so all zones (on different ports) can load the script

Dev harness: http://localhost:3006

Config file: apps/nav-component/vite.config.ts

export default defineConfig({
  build: {
    lib: {
      entry: "src/nav.ts",
      formats: ["es"],
      fileName: () => "nav.js",
    },
  },
  server: { port: 3006, strictPort: true, cors: true },
});

Next.js App — Port 3001

A standalone Next.js application integrated via the Multi-Zones pattern.

  • basePath: "/nextapp" — all routes live under /nextapp
  • assetPrefix: "/nextapp" — static assets load from /nextapp/_next/...
  • Loads <app-nav> via next/script in its own layout.tsx

URL via shell: http://localhost:3000/nextapp Direct URL: http://localhost:3001/nextapp


React App — Port 3002

A client-side React application built with Vite.

  • base: "/reactapp/" in Vite config ensures correct asset resolution
  • <app-nav> loaded in index.html before the React root

URL via shell: http://localhost:3000/reactapp Direct URL: http://localhost:3002/reactapp/


SvelteKit App — Port 3003

A SvelteKit application using the auto adapter.

  • paths.base: "/svelteapp" in SvelteKit config prefixes all routes and assets
  • <app-nav> loaded in app.html (SvelteKit's HTML template)

URL via shell: http://localhost:3000/svelteapp Direct URL: http://localhost:3003/svelteapp


Vue App — Port 3004

A Vue 3 application built with Vite.

  • base: "/vueapp/" in Vite config ensures correct asset resolution
  • <app-nav> loaded in index.html before the Vue mount point

URL via shell: http://localhost:3000/vueapp Direct URL: http://localhost:3004/vueapp/


Angular App — Port 3005

An Angular application (v21) using the Angular CLI.

  • baseHref: "/angularapp/" in angular.json prefixes all asset paths
  • <app-nav> loaded in src/index.html before <app-root>

URL via shell: http://localhost:3000/angularapp Direct URL: http://localhost:3005/angularapp


Shared Package

The @micro-frontend/shared package (packages/shared/) provides shared constants consumed by any app in the monorepo.

Exports

Export Type Description
APP_NAME string Application title: "Micro Frontend PoC"
APPS readonly AppInfo[] Registry of all micro frontends with name, path, and framework
COLORS Record<string, string> Framework brand colors for UI styling
AppInfo type TypeScript type for an app entry

This package has no build step — it exports raw TypeScript files. It is used by the shell's home page for the card grid display. Navigation links are not sourced from this package — each zone defines its own links attribute on the <app-nav> element.


How Routing Works

Next.js Multi-Zones

The Next.js app (next-app) is integrated using the Multi-Zones pattern:

  1. The child app sets basePath: "/nextapp" and assetPrefix: "/nextapp"
  2. The shell rewrites /nextapp/** requests to the child app's server (localhost:3001)
  3. Next.js handles zone merging — pages, API routes, and static assets all resolve correctly

The rewrite is placed in beforeFiles so it takes priority over the shell's own filesystem routes.

Reverse Proxy Rewrites

Non-Next.js apps (React, Svelte, Vue, Angular) cannot use Multi-Zones directly. The shell uses rewrites() in the fallback section to proxy requests:

Browser → localhost:3000/reactapp   → rewrite → localhost:3002/reactapp/
Browser → localhost:3000/svelteapp  → rewrite → localhost:3003/svelteapp
Browser → localhost:3000/vueapp     → rewrite → localhost:3004/vueapp/
Browser → localhost:3000/angularapp → rewrite → localhost:3005/angularapp/

Base Path Configuration

Every micro frontend must know its own base path so it generates correct asset URLs.

Framework Config Property Value
Next.js basePath + assetPrefix "/nextapp"
React/Vite base "/reactapp/"
SvelteKit paths.base "/svelteapp"
Vue/Vite base "/vueapp/"
Angular baseHref "/angularapp/"

Why a Web Component Nav

The problem with a shared npm package

If the nav is a shared npm package (e.g., @yourco/config-layout) compiled into each zone's bundle:

  • A nav change forces every zone to rebuild and redeploy
  • This breaks the "independent deploy" promise of micro frontends
  • Teams cannot ship a nav update without coordinating with all zone owners

The Web Component solution

The nav is built as a standalone Web Component deployed to its own URL:

  • A nav change deploys one artifact (nav.js) — zero zone rebuilds
  • Zones pick up the updated nav on the next page load
  • No npm dependency, no build-time coupling, no version coordination
  • Works across any framework — React, Vue, Svelte, Angular, vanilla HTML

Before vs. After

Aspect Shared npm package Web Component
Nav change deploys All zones must rebuild Only nav-component redeploys
Build-time coupling Yes (compiled into each bundle) None (loaded at runtime)
Framework requirement Must match host framework Framework-agnostic (custom element)
Style isolation CSS-in-JS or careful scoping Shadow DOM (automatic)
Independent deployment Broken for nav changes Fully preserved

Deploy Independence Matrix

Every change touches exactly one app:

What changed Who redeploys Zone rebuilds?
Nav styles / structure nav-component only Zero
Course Planner adds route course-planner only Zero
React app bug fix react-app only Zero
Shell rewrite rules shell only Zero
Shared constants Affected consumers Only those

Adding a New Micro Frontend

To add a new micro frontend (e.g., a Remix app on port 3007):

1. Create the app

mkdir apps/remix-app
cd apps/remix-app
# Initialize your framework of choice

Ensure the package.json has a dev script:

{
  "name": "@micro-frontend/remix-app",
  "scripts": { "dev": "remix dev --port 3007" }
}

2. Set the base path

Configure the framework to serve under /remixapp.

3. Add the Web Component nav

In the app's HTML entry point, add:

<script type="module" src="http://localhost:3006/src/nav.ts"></script>
<app-nav
  app-name="Micro Frontend PoC"
  links='[{"label":"Home","href":"/"},{"label":"Remix App","href":"/remixapp"}]'
></app-nav>

4. Add rewrite rules to the shell

In apps/shell/next.config.ts, add entries to the fallback array:

{ source: "/remixapp", destination: "http://localhost:3007/remixapp" },
{ source: "/remixapp/:path*", destination: "http://localhost:3007/remixapp/:path*" },

5. Run

pnpm install
pnpm dev

Turborepo automatically picks up the new app since pnpm-workspace.yaml includes apps/*.


Production Considerations

In production, the nav component should be built (vite build outputs dist/nav.js) and deployed to a CDN or its own static hosting:

https://nav.yourcompany.com/nav.js

Each zone's HTML would reference the production URL instead of localhost:3006:

<script type="module" src="https://nav.yourcompany.com/nav.js"></script>

Use an edge router or reverse proxy

Replace the Next.js rewrite-based proxying with a dedicated routing layer:

  • Nginx / Caddy — configure location blocks per path prefix
  • AWS Amplify — one Amplify app per zone, webhook-based cascade triggers
  • Vercel — use vercel.json rewrites with each app deployed independently
  • Cloudflare Workers / AWS CloudFront — route by path prefix at the edge

CI/CD with Amplify (monorepo)

For AWS Amplify deployments from a monorepo:

  1. Each zone is a separate Amplify app with AMPLIFY_MONOREPO_APP_ROOT pointing to its directory
  2. Amplify natively detects Turborepo and only rebuilds when files inside appRoot change
  3. Shared package changes (outside any appRoot) require incoming webhooks + GitHub Actions:
# .github/workflows/deploy.yml
- name: Check if shared package changed
  run: |
    CHANGED=$(git diff --name-only HEAD^1 HEAD)
    if echo "$CHANGED" | grep -q "packages/"; then
      echo "changed=true" >> $GITHUB_OUTPUT
    fi

- name: Trigger zone rebuild
  if: steps.check.outputs.changed == 'true'
  run: curl -X POST "${{ secrets.AMPLIFY_WEBHOOK_ZONE }}"

Independent deployments

Each micro frontend is deployable independently:

pnpm --filter @micro-frontend/react-app build

Shared state

This PoC does not implement cross-app state sharing. For production, consider:

  • URL parameters / query strings for simple data passing
  • Browser storage (localStorage, sessionStorage) for shared state
  • Custom events (window.postMessage, CustomEvent) for inter-app communication
  • A shared auth layer (cookies, JWT tokens) for authentication state

Troubleshooting

Port already in use

If you see Error: Port XXXX is already in use, kill the process occupying that port:

# macOS / Linux
lsof -ti:3000 | xargs kill -9

# Windows
netstat -ano | findstr :3000
taskkill /PID <PID> /F

Or kill all dev servers at once:

# macOS / Linux
for port in 3000 3001 3002 3003 3004 3005 3006; do
  lsof -ti:$port | xargs kill -9 2>/dev/null
done

# Windows (PowerShell)
3000..3006 | ForEach-Object {
  $connections = Get-NetTCPConnection -LocalPort $_ -ErrorAction SilentlyContinue
  $connections | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }
}

The <app-nav> Web Component requires the nav-component dev server to be running on port 3006. If the nav bar is missing:

  1. Ensure port 3006 is active: curl http://localhost:3006
  2. Check the browser console for script loading errors
  3. If running a single zone, start the nav component too: pnpm --filter @micro-frontend/nav-component dev

HMR (Hot Module Replacement) not working

When accessing micro frontends through the shell proxy (localhost:3000/reactapp), HMR WebSocket connections may not be proxied. Two options:

  1. Open the app directly on its own port for development (e.g., localhost:3002/reactapp/)
  2. Configure HMR port/host in the Vite config:
server: {
  port: 3002,
  strictPort: true,
  hmr: { port: 3002 },
}

Svelte app returns blank page through shell

SvelteKit may redirect from /svelteapp to /svelteapp/ (with trailing slash). The rewrite rules handle both patterns. Check the browser's Network tab for redirect chains.

Angular dev server slow to start

Angular's build system compiles the full application on startup. The first load takes longer than Vite-based apps. Subsequent reloads are fast due to incremental compilation.

pnpm install fails

Ensure you're using a compatible pnpm version. The project specifies "packageManager": "[email protected]" in the root package.json. Use Corepack to match:

corepack enable
corepack install

Tech Stack

Layer Technology
Monorepo Turborepo + pnpm workspaces
Shell Next.js 15 (App Router)
Nav Component Vanilla TypeScript + Vite (Web Component)
Micro Frontend 1 Next.js 15 (Multi-Zones)
Micro Frontend 2 React 19 + Vite 6
Micro Frontend 3 SvelteKit 2 + Svelte 5
Micro Frontend 4 Vue 3 + Vite 6
Micro Frontend 5 Angular 21
Shared Package TypeScript (no build step)

Top categories

Loading Svelte Themes