Skip to content

Installation - ReactTracer with Next.js App Router ​

Install and configure ReactTracer in a Next.js application using the App Router with React Server Components.

Prerequisites ​

  • Node.js 18+
  • Next.js 13+ (App Router)
  • React 18+
  • React DevTools (browser extension or development mode)

Installation ​

bash
# Install runtime and Babel plugin
pnpm add @autotracer/react18
pnpm add -D @autotracer/plugin-babel-react18

# Using npm
npm install @autotracer/react18
npm install --save-dev @autotracer/plugin-babel-react18

# Using yarn
yarn add @autotracer/react18
yarn add -D @autotracer/plugin-babel-react18

For browser-based internal web apps, the normal control surface is the Dashboard. On the App Router Babel path, mount it manually when you want that workflow:

bash
pnpm add -D @autotracer/dashboard

Configuration ​

1. Configure Babel Plugin ​

Create or update babel.config.js in your project root:

javascript
// babel.config.js
module.exports = {
  presets: ["next/babel"],
  plugins: [
    [
      "@autotracer/plugin-babel-react18",
      {
        serverComponents: true, // Important for App Router!
        include: { paths: ["app/**", "components/**", "src/**"] },
        exclude: { paths: ["**/*.test.*", "**/*.spec.*"] },
      },
    ],
  ],
};

Critical Setting:

  • serverComponents: true - Only transforms files with "use client" directive

2. Create Client Provider ​

Create app/AutoTracerBootstrap.tsx:

typescript
// app/AutoTracerBootstrap.tsx
"use client";

import { Suspense, lazy, type ReactNode } from "react";

const isDevelopment = process.env.NODE_ENV === "development";
const isInternalQa = process.env.NEXT_PUBLIC_INTERNAL_QA === "true";

const AutoTracerBoundary = lazy(async () => {
  if (isDevelopment || isInternalQa) {
    const { isRenderMonitorActive, reactTracer } = await import(
      "@autotracer/react18",
    );

    if (!isRenderMonitorActive()) {
      reactTracer({
        enabled: isDevelopment,
      });
    }
  }

  return {
    default: ({ children }: { children: ReactNode }) => <>{children}</>,
  };
});

export function AutoTracerBootstrap({ children }: { children: ReactNode }) {
  return (
    <Suspense fallback={null}>
      <AutoTracerBoundary>{children}</AutoTracerBoundary>
    </Suspense>
  );
}

ReactTracer starts dormant by default. This setup starts tracing immediately in development and leaves the runtime dormant in restricted internal QA builds unless you opt in deliberately.

If this internal browser setup uses dormant startup, mount the Dashboard in that same bootstrap path when you want the standard browser control surface:

tsx
const { mountDashboard } = await import("@autotracer/dashboard");
mountDashboard();

Use Dashboard Package Reference for mountDashboard(...) behavior. For package-side widget settings, use Dashboard enabled, Dashboard hideByDefault, Dashboard position, and Dashboard hotkeys.

Use the lower-level globalThis.autoTracer.reactTracer API in tests, automation, and other non-Dashboard setups.

Immediate startup can overwhelm DevTools

Immediate startup is useful for confirming setup in a small app. In a large app or a provider-heavy page, tracing from bootstrap can generate enough render output to flood DevTools before you even reach the interaction you care about. Use enabled: true at startup with care. If the site is large, prefer dormant startup and a narrow capture window through the Dashboard instead of tracing the whole app from first render.

3. Update Root Layout ​

Update app/layout.tsx:

typescript
// app/layout.tsx
import type { ReactNode } from "react";
import { AutoTracerBootstrap } from "./AutoTracerBootstrap";

export default function RootLayout({
  children,
}: {
  children: ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <AutoTracerBootstrap>{children}</AutoTracerBootstrap>
      </body>
    </html>
  );
}

4. Create a Client Component ​

Create app/counter.tsx:

typescript
// app/counter.tsx
"use client";

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState("Hello");

  return (
    <div>
      <h2>{message}</h2>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setMessage("Updated!")}>Change Message</button>
    </div>
  );
}

5. Use in Page ​

Update app/page.tsx:

typescript
// app/page.tsx
import { Counter } from "./counter";

export default function Home() {
  return (
    <main>
      <h1>Next.js App Router with ReactTracer</h1>
      <Counter />
    </main>
  );
}

Running Your App ​

bash
# Development mode
pnpm dev

# Or with npm
npm run dev

Open your browser to http://localhost:3000 and check the console—you should see React component lifecycle traces from client components.

What You'll See ​

When client components render, the console shows:

Component mount: Counter
  State: count = 0
  State: message = "Hello"

Component update: Counter
  State: count = 1 (changed from 0)
  State: message = "Hello"

Note: Server Components won't be traced (they don't have "use client" directive).

Server Components vs Client Components ​

Server Components (Not Traced) ​

typescript
// app/server-component.tsx
// NO "use client" directive - this is a Server Component

export function ServerComponent() {
  // Runs on server only, not traced
  return <div>Server rendered content</div>;
}

Client Components (Traced) ​

typescript
// app/client-component.tsx
"use client"; // This directive enables tracing

import { useState } from "react";

export function ClientComponent() {
  // Runs in browser, traced by ReactTracer
  const [state] = useState(0);
  return <div>{state}</div>;
}

Development Mode ​

Next.js Fast Refresh automatically provides the React DevTools hook, so tracing works out of the box during development.

Restricted Test/QA Deployments ​

Do not include tracing in publicly accessible builds. If you deploy tracing to a restricted internal test/QA environment, you still need one of these DevTools hook sources:

Install the React DevTools browser extension for your browser.

Option 2: DevTools Hook Shim ​

Create a client component for the hook:

typescript
// app/devtools-hook.tsx
"use client";

import { useEffect } from "react";

export function DevToolsHook() {
  useEffect(() => {
    if (
      typeof window !== "undefined" &&
      !window.__REACT_DEVTOOLS_GLOBAL_HOOK__
    ) {
      let nextID = 0;
      window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
        renderers: new Map(),
        supportsFiber: true,
        inject: function (injected) {
          return nextID++;
        },
        onScheduleFiberRoot: function () {},
        onCommitFiberRoot: function () {},
        onCommitFiberUnmount: function () {},
      };
    }
  }, []);

  return null;
}

Then include it in your root layout:

typescript
// app/layout.tsx
import { AutoTracerBootstrap } from "./AutoTracerBootstrap";
import { DevToolsHook } from "./devtools-hook";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <DevToolsHook />
        <AutoTracerBootstrap>{children}</AutoTracerBootstrap>
      </body>
    </html>
  );
}

Controlling Tracing ​

Disable for Specific Components ​

Use the @trace-disable pragma:

typescript
"use client";

// @trace-disable
export function HighFrequencyComponent() {
  const [value, setValue] = useState(0);
  return <div>{value}</div>;
}

Opt-in Mode ​

Switch to opt-in mode in babel.config.js:

javascript
{
  "plugins": [
    [
      "@autotracer/plugin-babel-react18",
      {
        "mode": "opt-in",
        "serverComponents": true
      }
    ]
  ]
}

Then use @trace pragma:

typescript
"use client";

// @trace
export function TracedComponent() {
  const [state] = useState(0);
  return <div>{state}</div>;
}

Note: include/exclude component filters define the eligible set and are evaluated before pragmas. @trace only operates within the eligible set and cannot rescue a component that misses an include.components filter or matches an exclude.components filter. For exact placement and precedence rules, see Pragma Comments.

Environment-based Control ​

javascript
// babel.config.js
const plugins = [];

if (process.env.ENABLE_TRACING === "1") {
  plugins.push([
    "@autotracer/plugin-babel-react18",
    {
      mode: "opt-out",
      serverComponents: true,
      include: { paths: ["app/**", "components/**"] },
    },
  ]);
}

module.exports = {
  presets: ["next/babel"],
  plugins,
};

Selective Component Tracing ​

javascript
// babel.config.js
{
  "plugins": [
    [
      "@autotracer/plugin-babel-react18",
      {
        "mode": "opt-out",
        "serverComponents": true,
        "include": { "paths": ["app/components/interactive/**"] },
        "exclude": { "paths": ["app/components/static/**"] }
      }
    ]
  ]
}

Integration with FlowTracer ​

Combine ReactTracer with FlowTracer for complete visibility:

bash
pnpm add @autotracer/flow
pnpm add -D @autotracer/plugin-babel-flow

@autotracer/logger installs automatically with @autotracer/flow.

javascript
// babel.config.js
module.exports = {
  presets: ["next/babel"],
  plugins: [
    [
      "@autotracer/plugin-babel-react18",
      {
        mode: "opt-out",
        serverComponents: true,
        include: { paths: ["app/**", "components/**"] },
      },
    ],
    [
      "@autotracer/plugin-babel-flow",
      {
        include: {
          paths: ["app/api/**", "lib/**", "utils/**"],
          functions: ["*"],
        },
      },
    ],
  ],
};
typescript
// app/providers.tsx
"use client";

import { Suspense, lazy, type ReactNode } from "react";

const isDevelopment = process.env.NODE_ENV === "development";
const isInternalQa = process.env.NEXT_PUBLIC_INTERNAL_QA === "true";

const AutoTracerBoundary = lazy(async () => {
  if (isDevelopment || isInternalQa) {
    await import("@autotracer/flow");
    await import("@autotracer/flow/runtime");

    const { isRenderMonitorActive, reactTracer } = await import(
      "@autotracer/react18",
    );

    if (!isRenderMonitorActive()) {
      reactTracer({
        enabled: isDevelopment,
      });
    }
  }

  return {
    default: ({ children }: { children: ReactNode }) => <>{children}</>,
  };
});

export function Providers({ children }: { children: ReactNode }) {
  return (
    <Suspense fallback={null}>
      <AutoTracerBoundary>{children}</AutoTracerBoundary>
    </Suspense>
  );
}

Troubleshooting ​

Client Components Not Traced ​

Check:

  1. Component file has "use client" directive at the top
  2. serverComponents: true in Babel config
  3. reactTracer() called in providers
  4. React DevTools hook available

Server Components Traced (Unexpected) ​

Issue: Server Components shouldn't be traced.

Solution: Ensure serverComponents: true in Babel config:

javascript
{
  "serverComponents": true
}

Build Errors ​

Common issues:

  • Missing "use client" directive - add it to client components
  • Missing next/babel preset - add it first in presets array
  • Plugin not installed - run pnpm add -D @autotracer/plugin-babel-react18

Hydration Errors ​

If you get hydration mismatches:

  1. Ensure reactTracer() called in useEffect (not during render)
  2. Check no tracing output during SSR
  3. Verify client/server boundaries correct

Fast Refresh Issues ​

If Fast Refresh stops working:

  1. Restart Next.js dev server
  2. Clear the .next cache directory
  3. Verify Babel config syntax

Best Practices ​

✅ Do: ​

  • Use "use client" for interactive components
  • Initialize reactTracer() in the client bootstrap boundary, not during render
  • Use serverComponents: true in Babel config

❌ Don't: ​

  • Add "use client" to Server Components unnecessarily
  • Call reactTracer() during render (causes hydration errors)

Next Steps ​

See Also ​

Released under the MIT License.