apps/docs/content/docs/guides/microfrontends.mdx
Microfrontends are an architectural pattern where a web application is decomposed into smaller, independently developed and deployed applications that work together.
Turborepo provides built-in support for running vertical microfrontends (sometimes called "zones") locally during development through an integrated proxy server. The proxy coordinates multiple applications and routes traffic between them.
Let's imagine you have a monorepo with multiple frontend applications:
<Files> <Folder name="apps" defaultOpen> <Folder name="web"> <File name="package.json" /> <File name="next.config.js" /> </Folder> <Folder name="docs"> <File name="package.json" /> <File name="next.config.js" /> </Folder> <Folder name="marketing"> <File name="package.json" /> <File name="vite.config.ts" /> </Folder> </Folder> <File name="package.json" /> <File name="turbo.json" /> </Files>In production, these applications might be deployed separately and composed together using a reverse proxy or edge routing. But during development, you want to:
http://localhost:3024)Turborepo provides a built-in proxy server that automatically routes traffic between your microfrontend applications during development. The proxy reads a microfrontends.json configuration file and starts when you run turbo dev.
microfrontends.jsonCreate a microfrontends.json file in your parent application. This application is the one that all requests will fall through to when not matched by another application.
{
"$schema": "https://turborepo.dev/microfrontends/schema.json",
"applications": {
"web": {
"development": {
"local": {
"port": 3000
}
}
},
"docs": {
"development": {
"local": {
"port": 3001
}
},
"routing": [
{
"paths": ["/docs", "/docs/:path*"]
}
]
}
}
}
Next, use the turbo get-mfe-port command to set the ports in your application's development scripts. This command injects the port number when running turbo dev:
<Tabs items={['Next.js', 'Vite']} storageKey="framework-preference">
<Tab value="Next.js">{
"scripts": {
"dev": "next dev --port $(turbo get-mfe-port)"
}
}
{
"scripts": {
"dev": "vite dev --port $TURBO_MFE_PORT"
}
}
If you're using a framework, set the base path according to the needs of the framework.
<Tabs items={['Next.js', 'Vite']} storageKey="framework-preference">
<Tab value="Next.js">import type { NextConfig } from "next";
const nextConfig: NextConfig = {
basePath: "/docs",
};
export default nextConfig;
import { defineConfig } from "vite";
export default defineConfig({
base: "/admin",
});
Visit the Microfrontends section of your framework for framework-specific guidance.
turbo devWhen you run turbo dev, Turborepo will:
3024)TURBO_MFE_PORT environment variable in tasks to set the applications' portNow you can access all of your microfrontends applications at http://localhost:3024.
Each application in the applications object can have the following properties:
If not provided, the application key is used to match the name in `package.json.
development.localThe port where the application runs locally.
{
"development": {
"local": {
"port": 3000
}
}
}
If you omit the port, Turborepo will generate a deterministic port based on the application name (between 3000-8000).
development.fallbackOptionally provide a target to proxy to when an application is not running locally. This is most frequently used to route to production to make commands like turbo dev --filter=web that only run a subset of applications a seamless experience.
{
"development": {
"fallback": "example.com"
}
}
routingAn array of path groups that should route to this application. If no routing is provided, the application becomes the default application that catches all unmatched routes.
Only one application can have no routing configuration. This is your "root" application that handles all routes not matched by other applications.
{
"routing": [
{
"paths": ["/api/:version/users/:id"]
},
{
"paths": ["/docs", "/docs/:path*"]
}
]
}
/Blog and /blog are different routes/home and /home/ match the same route{
"paths": ["/pricing", "/about", "/contact"]
}
These paths match exactly as written.
{
"paths": ["/blog/:slug", "/users/:id/profile"]
}
Segments starting with : match any single path segment. For example, /blog/:slug matches /blog/hello but not /blog/hello/world.
{
"paths": ["/docs/:path*", "/api/:version*"]
}
Parameters ending with * match zero or more path segments. For example, /docs/:path* matches /docs, /docs/intro, and /docs/api/reference.
You can also use the + modifier to match one or more segments (excluding the empty path):
{
"paths": ["/api/:path+"]
}
This matches /api/users and /api/users/123, but not /api or /api/.
You can define sophisticated routing with nested parameters:
{
"routing": [
{
"paths": [
"/api/:version/users",
"/api/:version/users/:id",
"/api/:version/posts/:postId/comments/:commentId"
]
}
]
}
Organize routes into logical groups for better maintainability:
{
"$schema": "https://turborepo.dev/microfrontends/schema.json",
"applications": {
"marketing": {
"development": {
"local": 3002
},
"routing": [
{
"group": "blog",
"paths": ["/blog", "/blog/:slug*"]
},
{
"group": "sales",
"paths": ["/pricing", "/contact", "/demo"]
}
]
}
}
}
The group field is for organizational purposes and doesn't affect routing behavior.
packageNameOptionally use the name of the package from package.json in your workspace.
{
"applications": {
"main-site": {
"packageName": "web"
}
}
}
options.localProxyPortOptionally change the proxy port using the localProxyPort option
Defaults to 3024.
{
"options": {
"localProxyPort": 8080
}
}
The Turborepo microfrontends proxy is meant for local usage only. How you implement and integrate your production microfrontends depends on your production infrastructure. However, we can integrate your local and production environments to create a seamless experience across environments.
To start, we've built Turborepo's local proxy to integrate with Vercel's microfrontends. We look forward to working with any infrastructure providers that would also like to integrate.
Vercel has native support for microfrontends that provide:
Learn more in Vercel's documentation.
@vercel/microfrontendsIf you install @vercel/microfrontends in any package or add it to your workspace, Turborepo will automatically defer to it instead of using the built-in proxy. This allows for a gradual migration path.
You can use the same microfrontends.json configuration as for Turborepo. Turborepo's microfrontends.json schema is a subset of Vercel's schema, so it is compatible with @vercel/microfrontends.
To learn more about @vercel/microfrontends, visit the package on npm.
By default, the microfrontends proxy will try to use port 3024. If you already use that port for a different purpose, you can change Turborepo's port using the options.localProxyPort.
Ensure that the paths that the microfrontends matches for in its routing configuration include the routes for the assets. Check your network tab to find paths that are or aren't matching as expected.
If you use a Single-Page Application (SPA) link (like the Next.js <Link> component) to link across applications, this can result in errors. Even though the port and domain remain the same, the applications are different. This means the routing is, by definition, not a "single-page application".
You are still be able to reach a proxied port directly. For example, if localhost:3000 is proxied to localhost:3042, you can still visit localhost:3000 in your browser.
If you would like for localhost:3000 to redirect to localhost:3024, you must set this up manually in your application.
Verify that:
packageName matches your actual package nametask specified exists in the package's package.jsonturbo get-mfe-port not workingIf you're getting an error when running turbo get-mfe-port, ensure that:
name field in its package.jsonmicrofrontends.json file exists in one of the packagesapplications section of microfrontends.json--skip-infer, you must also specify --cwd pointing to your repository root:turbo --skip-infer --cwd ../.. get-mfe-port
Here's a full example of a microfrontends configuration for an e-commerce platform:
{
"$schema": "https://turborepo.dev/microfrontends/schema.json",
"options": {
"localProxyPort": 3024
},
"applications": {
"web": {
"packageName": "web",
"development": {
"local": {
"port": 3000
}
}
},
"docs": {
"packageName": "documentation",
"development": {
"local": {
"port": 3001
}
},
"routing": [
{
"group": "documentation",
"paths": [
"/docs",
"/docs/:path*",
"/api-reference",
"/api-reference/:path*"
]
}
]
},
"blog": {
"development": {
"local": {
"port": 3002
}
},
"routing": [
{
"group": "content",
"paths": [
"/blog",
"/blog/:slug",
"/blog/category/:category",
"/authors/:author"
]
}
]
},
"shop": {
"development": {
"local": {
"port": 3003
}
},
"routing": [
{
"group": "commerce",
"paths": [
"/products",
"/products/:id",
"/cart",
"/checkout",
"/orders/:orderId"
]
}
]
}
}
}
With this configuration:
web app handles the homepage and any unmatched routesdocs app handles all documentationblog app handles blog posts and author pagesshop app handles e-commerce functionality