Micro-Frontends
How τjs uses build-time orchestration to support multiple frontend applications.
Overview
Section titled “Overview”τjs enables multiple frontend applications in a single deployment through build-time composition and server-side routing. Unlike runtime federation systems, τjs builds each application separately and routes requests at the HTTP layer.
Key characteristics:
- Each app has its own Vite build
- Each app has its own bundle
- Server routes requests to the correct app
- No runtime coordination between apps
- No shared runtime state
How It Works
Section titled “How It Works”Build Time
Section titled “Build Time”When you run taujs build:
-
Vite builds each app separately
- Each
entryPointdirectory is built independently - Produces separate bundles with hashed filenames
- Generates manifests for each app
- Each
-
Assets organised per app
dist/client/ ├── app/ # Customer app bundle │ ├── assets/ │ ├── manifest.json │ └── index.html └── admin/ # Admin app bundle ├── assets/ ├── manifest.json └── index.html- Tree-shaking per app
- Vite analyses each app’s imports
- Only includes code that app uses
- Shared dependencies optimised automatically
Runtime
Section titled “Runtime”When a request arrives:
- Fastify receives request
GET /admin/users- τjs matches route
// Finds matching route in config { path: '/admin/:section', appId: 'admin' }-
Server loads correct app
- Reads
adminapp’s manifests - Loads SSR bundle for admin app
- Uses admin app’s assets
- Reads
-
Response delivered
- HTML rendered with admin app code
- Client receives only admin bundle
- No customer app code sent
No runtime coordination - each request is independent.
Configuration
Section titled “Configuration”Define multiple apps in your τjs config:
import { defineConfig } from "@taujs/server/config";
export default defineConfig({ apps: [ { appId: "customer", entryPoint: "app", routes: [ { path: "/app/:feature?/:id?", attr: { render: "streaming", middleware: { auth: { strategy: "jwt" } }, }, }, ], }, { appId: "admin", entryPoint: "admin", routes: [ { path: "/admin/:section?/:id?", attr: { render: "ssr", middleware: { auth: { strategy: "session", roles: ["admin", "superadmin"], }, }, }, }, ], }, { appId: "docs", entryPoint: "docs", routes: [ { path: "/docs/:slug*", attr: { render: "ssr", hydrate: true, }, }, ], }, ],});Project Structure
Section titled “Project Structure”project/├── client/│ ├── app/ # Customer application│ │ ├── entry-client.tsx│ │ ├── entry-server.tsx│ │ ├── index.html│ │ └── App.tsx│ ├── admin/ # Admin application│ │ ├── entry-client.tsx│ │ ├── entry-server.tsx│ │ ├── index.html│ │ └── App.tsx│ ├── docs/ # Documentation application│ │ ├── entry-client.tsx│ │ ├── entry-server.tsx│ │ └── index.html│ └── shared/ # Shared code (optional)│ ├── components/│ ├── hooks/│ └── utils/├── server/│ ├── index.ts│ └── services/└── taujs.config.tsBuild Process Details
Section titled “Build Process Details”What Happens During Build
Section titled “What Happens During Build”npm run buildFor each app:
-
Client build (
dist/client/{entryPoint}/)- Vite scans
entry-client.tsx - Bundles all imported code
- Outputs hashed assets
- Generates
manifest.json
- Vite scans
-
SSR build (
dist/ssr/{entryPoint}/)- Vite scans
entry-server.tsx - Bundles SSR-compatible code
- Outputs
server.js - Generates
ssr-manifest.json
- Vite scans
-
Dependency resolution
- Shared dependencies (React) included in each bundle
- Vite optimises bundle splitting automatically
- No manual shared chunk configuration needed
Bundle Optimisation
Section titled “Bundle Optimisation”Shared Dependencies
Section titled “Shared Dependencies”Dependencies appear in each app’s bundle based on imports:
// Customer app importsimport React from "react";import { useQuery } from "@tanstack/react-query";
// Admin app importsimport React from "react";import { create } from "zustand";Result:
- Both bundles include React (shared dependency)
- Customer bundle includes react-query
- Admin bundle includes zustand
- No runtime coordination needed
Tree-Shaking
Section titled “Tree-Shaking”Vite tree-shakes per app:
export function formatDate(date: Date) { /* ... */}export function formatCurrency(amount: number) { /* ... */}export function parseJSON(str: string) { /* ... */}// Customer app only imports formatDateimport { formatDate } from "@shared/utils";
// Customer bundle only includes formatDate// formatCurrency and parseJSON are tree-shaken awayNavigation Between Apps
Section titled “Navigation Between Apps”Full Page Navigation
Section titled “Full Page Navigation”Navigating between apps requires a full page load:
// Customer app<a href="/admin/users">Go to Admin</a>// Full page load when clickedWhy: Different apps = different bundles. Browser must load new bundle.
Client-Side Routing Within Apps
Section titled “Client-Side Routing Within Apps”Within an app, use client-side routing:
// Customer app - same bundleimport { BrowserRouter, Route, Link } from "react-router-dom";
function CustomerApp() { return ( <BrowserRouter> <Link to="/app/dashboard">Dashboard</Link> {/* No page reload */} <Link to="/app/settings">Settings</Link> {/* No page reload */} <Routes> <Route path="/app/dashboard" element={<Dashboard />} /> <Route path="/app/settings" element={<Settings />} /> </Routes> </BrowserRouter> );}Common Patterns
Section titled “Common Patterns”Marketing Site + Web App
Section titled “Marketing Site + Web App”apps: [ { appId: "marketing", entryPoint: "marketing", routes: [ { path: "/", attr: { render: "ssr", hydrate: false } }, { path: "/pricing", attr: { render: "ssr", hydrate: true } }, { path: "/about", attr: { render: "ssr", hydrate: false } }, ], }, { appId: "app", entryPoint: "app", routes: [ { path: "/app/:page*", attr: { render: "streaming", middleware: { auth: {} }, }, }, ], },];Admin + Customer Apps
Section titled “Admin + Customer Apps”apps: [ { appId: "customer", entryPoint: "app", routes: [ { path: "/app/:page*", attr: { render: "streaming", middleware: { auth: { strategy: "jwt" } }, }, }, ], }, { appId: "admin", entryPoint: "admin", routes: [ { path: "/admin/:section*", attr: { render: "ssr", middleware: { auth: { strategy: "session", roles: ["admin"], }, }, }, }, ], },];When to Use Multiple Apps
Section titled “When to Use Multiple Apps”Use multiple apps when:
- Clear domain boundaries (customer vs admin vs marketing)
- Different teams own different parts
- Different deployment schedules needed
- Security isolation required (admin code never sent to customers)
- Different performance characteristics
Use single app when:
- Application is cohesive with no clear boundaries
- Small team maintaining everything
- Shared navigation and state throughout
- Similar performance needs across all pages