Vercel Logo

Configuration Basics

Add the routing configuration that tells Vercel which paths go to which apps.

Outcome

Configure microfrontends.json for routing and wrap each Next.js config with withMicrofrontends to prevent asset conflicts.

Two Configuration Files

Microfrontends require two types of configuration:

  1. microfrontends.json - Defines routing (which paths go to which apps)
  2. next.config.ts wrapper - Configures asset prefixes to prevent collisions

Fast Track

  1. Install @vercel/microfrontends in each app
  2. Create microfrontends.json in the marketing (default) app
  3. Wrap each next.config.ts with withMicrofrontends

Hands-on Exercise 2.1

Configure the Acme Platform for microfrontends.

Part 1: Install the Package

Add @vercel/microfrontends to each application using pnpm's workspace filter:

pnpm add @vercel/microfrontends --filter @acme/marketing
pnpm add @vercel/microfrontends --filter @acme/docs
pnpm add @vercel/microfrontends --filter @acme/dashboard

This installs the package in all three apps.

Part 2: Create microfrontends.json

Create apps/marketing/microfrontends.json:

apps/marketing/microfrontends.json
{
  "$schema": "https://openapi.vercel.sh/microfrontends.json",
  "applications": {
    "@acme/marketing": {
      "development": {
        "fallback": "http://localhost:3000",
        "local": 3000
      }
    },
    "@acme/docs": {
      "routing": [
        {
          "paths": ["/docs", "/docs/:path*"]
        }
      ],
      "development": {
        "local": 3001
      }
    },
    "@acme/dashboard": {
      "routing": [
        {
          "paths": ["/app", "/app/:path*", "/settings", "/settings/:path*"]
        }
      ],
      "development": {
        "local": 3002
      }
    }
  }
}

Note: application names must match the name in each app's package.json. The default app uses development.fallback instead of routing. It catches everything not claimed by other apps.

Part 3: Wrap Next.js Configs

Update each application's next.config.ts to use the wrapper.

Marketing (apps/marketing/next.config.ts):

apps/marketing/next.config.ts
import type { NextConfig } from "next";
import { withMicrofrontends } from "@vercel/microfrontends/next/config";
 
const nextConfig: NextConfig = {
  // Your existing config
};
 
export default withMicrofrontends(nextConfig);

Docs (apps/docs/next.config.ts):

apps/docs/next.config.ts
import type { NextConfig } from "next";
import { withMicrofrontends } from "@vercel/microfrontends/next/config";
 
const nextConfig: NextConfig = {
  basePath: "/docs",
};
 
export default withMicrofrontends(nextConfig);

Dashboard (apps/dashboard/next.config.ts):

apps/dashboard/next.config.ts
import type { NextConfig } from "next";
import { withMicrofrontends } from "@vercel/microfrontends/next/config";
 
const nextConfig: NextConfig = {
  // No basePath - dashboard handles both /app/* and /settings/*
};
 
export default withMicrofrontends(nextConfig);

Use basePath when all routes share a common prefix (like /docs). Dashboard handles both /app/* and /settings/*, so no single basePath works. Routes are defined directly in the file structure.

Part 4: Update App Route Structure

Child apps use basePath to set their mount point, so file paths are relative to that base. When a request hits /docs/getting-started, it's routed to the docs app (which has basePath: "/docs"), and Next.js looks for a route at /getting-started within that app.

Docs app structure (apps/docs/app/):

apps/docs/app/
├── page.tsx              # handles /docs (the base path)
├── [slug]/
│   └── page.tsx          # handles /docs/:slug
└── layout.tsx

Create apps/docs/app/page.tsx:

apps/docs/app/page.tsx
export default function DocsHomePage() {
  return (
    <div className="max-w-4xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-2">Documentation</h1>
      <p className="text-gray-600 mb-8">
        Everything you need to build with Acme Platform.
      </p>
      <div className="grid gap-4">
        <a href="/docs/getting-started" className="block p-4 border rounded-lg hover:border-blue-500">
          <h2 className="font-semibold">Getting Started</h2>
          <p className="text-gray-600 text-sm">Learn the basics</p>
        </a>
        <a href="/docs/api" className="block p-4 border rounded-lg hover:border-blue-500">
          <h2 className="font-semibold">API Reference</h2>
          <p className="text-gray-600 text-sm">Complete API documentation</p>
        </a>
      </div>
    </div>
  );
}

Dashboard app structure (apps/dashboard/app/):

Without basePath, routes map directly to the file structure:

apps/dashboard/app/
├── app/
│   ├── page.tsx          # handles /app
│   └── projects/
│       └── page.tsx      # handles /app/projects
├── settings/
│   └── page.tsx          # handles /settings
└── layout.tsx

Create apps/dashboard/app/app/page.tsx:

apps/dashboard/app/app/page.tsx
export default function DashboardPage() {
  return (
    <div>
      <h1 className="text-2xl font-bold mb-6">Dashboard Overview</h1>
      <div className="grid md:grid-cols-3 gap-4">
        <div className="bg-white p-6 rounded-lg border">
          <h2 className="text-gray-500 text-sm mb-1">Total Projects</h2>
          <p className="text-3xl font-bold">12</p>
        </div>
        <div className="bg-white p-6 rounded-lg border">
          <h2 className="text-gray-500 text-sm mb-1">Active Deployments</h2>
          <p className="text-3xl font-bold">8</p>
        </div>
      </div>
    </div>
  );
}

Create apps/dashboard/app/settings/page.tsx:

apps/dashboard/app/settings/page.tsx
export default function SettingsPage() {
  return (
    <div>
      <h1 className="text-2xl font-bold mb-6">Settings</h1>
      <div className="bg-white rounded-lg border p-6 max-w-2xl">
        <h2 className="font-semibold mb-4">Account Settings</h2>
        <p className="text-gray-600">Manage your account settings here.</p>
      </div>
    </div>
  );
}

Why Asset Prefixes Matter

Without withMicrofrontends, every app serves static assets at /_next/:

marketing: /_next/static/chunks/main.js
docs:      /_next/static/chunks/main.js  ← CONFLICT!
dashboard: /_next/static/chunks/main.js  ← CONFLICT!

The wrapper adds unique prefixes (an obfuscated hash like vc-ap-b3331f):

marketing: /_next/static/chunks/main.js
docs:      /vc-ap-a1b2c3/_next/static/chunks/main.js
dashboard: /vc-ap-d4e5f6/_next/static/chunks/main.js

This prevents JavaScript and CSS from one app overwriting another's.

Try It

At this point, the apps still run independently. The configuration prepares them for the microfrontends proxy, which we'll enable in Lesson 2.3.

Run the apps:

pnpm dev

Visit each and verify the Header still renders correctly:

Commit

git add -A
git commit -m "feat: add microfrontends configuration"

Done-When

  • @vercel/microfrontends installed in all three apps
  • microfrontends.json created in marketing app with correct schema (using paths array)
  • Application names in microfrontends.json match package.json names
  • All next.config.ts files wrapped with withMicrofrontends
  • Docs app has basePath: "/docs" configured
  • Apps run without console errors

Troubleshooting

Asset 404s on /_next/ paths: You forgot withMicrofrontends in next.config.ts. Easy to miss.

does_not_match_schema error: You used path (string) instead of paths (array). The schema validator is picky about this.

application not_found error: The app name in microfrontends.json doesn't match package.json. Check for typos in @acme/docs vs docs.

What's Next

Configuration done. Next lesson covers path routing in detail, including the execution order that trips up most developers.