Skip to content

Microfrontends

This page covers browser-based React microfrontends that use @autotracer/plugin-vite-react18 for build-time injection and reactTracer() for runtime startup.

When a host app and its remotes share one browser runtime, all traced React components write to the same output stream. AutoTracer does not infer a microfrontend name automatically, so use the plugin-level prefix option when you need stable labels per build.

Vite Setup

Configure the host build and each remote build with reactTracer.vite(). Place the tracer plugin before the React plugin in every app that should inject labels.

typescript
// host/vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import federation from "@originjs/vite-plugin-federation";
import { reactTracer } from "@autotracer/plugin-vite-react18";

export default defineConfig(({ mode }) => {
  const isInternalBrowserBuild =
    mode === "development" || process.env.INTERNAL_QA === "true";

  return {
    plugins: [
      reactTracer.vite({
        inject: isInternalBrowserBuild,
        prefix: "host",
      }),
      react(),
      federation({
        name: "host",
        remotes: {
          catalog: "http://localhost:3001/assets/remoteEntry.js",
          cart: "http://localhost:3002/assets/remoteEntry.js",
        },
        shared: ["react", "react-dom"],
      }),
    ],
  };
});
typescript
// catalog/vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import federation from "@originjs/vite-plugin-federation";
import { reactTracer } from "@autotracer/plugin-vite-react18";

export default defineConfig(({ mode }) => {
  const isInternalBrowserBuild =
    mode === "development" || process.env.INTERNAL_QA === "true";

  return {
    plugins: [
      reactTracer.vite({
        inject: isInternalBrowserBuild,
        prefix: "catalog",
      }),
      react(),
      federation({
        name: "catalog",
        filename: "remoteEntry.js",
        exposes: {
          "./ProductCatalog": "./src/ProductCatalog",
        },
        shared: ["react", "react-dom"],
      }),
    ],
  };
});
typescript
// cart/vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import federation from "@originjs/vite-plugin-federation";
import { reactTracer } from "@autotracer/plugin-vite-react18";

export default defineConfig(({ mode }) => {
  const isInternalBrowserBuild =
    mode === "development" || process.env.INTERNAL_QA === "true";

  return {
    plugins: [
      reactTracer.vite({
        inject: isInternalBrowserBuild,
        prefix: "cart",
      }),
      react(),
      federation({
        name: "cart",
        filename: "remoteEntry.js",
        exposes: {
          "./ShoppingCart": "./src/ShoppingCart",
        },
        shared: ["react", "react-dom"],
      }),
    ],
  };
});

Runtime Startup

In the shared browser runtime, initialize reactTracer() before React renders. For restricted internal browser builds, start dormant and use the Dashboard workflow to target a narrow capture window.

typescript
// src/main.tsx
import { createRoot } from "react-dom/client";
import App from "./App";

const isInternalBrowserBuild =
  import.meta.env.DEV || import.meta.env.VITE_INTERNAL_QA === "true";

async function bootstrap(): Promise<void> {
  if (isInternalBrowserBuild) {
    const { reactTracer } = await import("@autotracer/react18");

    reactTracer({
      enabled: false,
    });
  }

  createRoot(document.getElementById("root")!).render(<App />);
}

void bootstrap();

Output Shape

Without prefix, all microfrontends share one output stream and the component names appear exactly as they are injected.

With prefix, the same shared output becomes easier to route by build:

Component render cycle 1:
├─ [host:App] Rendering ⚡
│   State change currentRoute: "/" → "/products"

Component render cycle 2:
├─ [catalog:ProductCatalog] Mount ⚡
│   Initial state products: []
│   Initial state loading: true

Component render cycle 3:
├─ [cart:ShoppingCart] Rendering ⚡
│   State change items: [] → [{id: "1", quantity: 1}]

AutoTracer does not add [host] or [remote] tags automatically. The trace rows reflect component names only, plus any build-time prefix values you configured.

Narrowing Scope

When one remote is noisy, narrow the injected set instead of tracing every microfrontend in the page.

typescript
// host/vite.config.ts
reactTracer.vite({
  prefix: "host",
});

// catalog/vite.config.ts
reactTracer.vite({
  prefix: "catalog",
});

// cart/vite.config.ts
reactTracer.vite({
  inject: false,
});

include and exclude filters are still evaluated before pragma comments. @trace only operates inside the eligible set.

Public Builds

Keep tracing out of publicly accessible builds. Guard inject so the host and each remote only instrument local development or restricted internal browser builds.

typescript
export default defineConfig(({ mode }) => {
  const isInternalBrowserBuild =
    mode === "development" || process.env.INTERNAL_QA === "true";

  return {
    plugins: [
      reactTracer.vite({
        inject: isInternalBrowserBuild,
        prefix: "host",
      }),
      react(),
    ],
  };
});

Next Steps

Released under the MIT License.