Next.js

Run the SDK in the browser from the App Router, and on the server in route handlers.

For the App Router in Next.js 13.4 and later. The browser side reports page views, uncaught errors and slow requests; the server side lets route handlers log. Install the SDK first: npm install apperio.

1. Add your keys

.env.local
NEXT_PUBLIC_APPERIO_API_KEY=your-api-key
NEXT_PUBLIC_APPERIO_PROJECT_ID=your-project-id

# Only needed for step 5, server logging
APPERIO_API_KEY=your-api-key
APPERIO_PROJECT_ID=your-project-id

The NEXT_PUBLIC_ prefix puts the values in the browser bundle, which the browser SDK needs. Set the same variables in your hosting provider.

2. Create the browser logger

Create the logger lazily, the first time it is needed in the browser. Creating it at module level would also run it during server rendering.

lib/apperio.ts
import { Apperio } from 'apperio';

let logger: Apperio | null = null;

/**
 * The browser logger, created on first use. Returns null during server
 * rendering and when the environment variables are missing.
 */
export function getLogger(): Apperio | null {
  if (typeof window === 'undefined') return null;
  if (logger) return logger;

  const apiKey = process.env.NEXT_PUBLIC_APPERIO_API_KEY;
  const projectId = process.env.NEXT_PUBLIC_APPERIO_PROJECT_ID;
  if (!apiKey || !projectId) return null;

  logger = new Apperio({
    apiKey,
    projectId,
    environment: process.env.NODE_ENV,
    serviceName: 'web',
  });
  return logger;
}

process.env.NODE_ENV is production in a production build, which is the environment new-error notifications follow by default.

3. Start it from the root layout

A client component that starts the logger and renders nothing:

components/apperio-provider.tsx
'use client';

import { useEffect } from 'react';
import { getLogger } from '@/lib/apperio';

export function ApperioProvider() {
  useEffect(() => {
    getLogger();
  }, []);

  return null;
}
app/layout.tsx
import type { ReactNode } from 'react';
import { ApperioProvider } from '@/components/apperio-provider';

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

The layout stays a server component. Client-side navigations keep the same logger and the same session, and each one is logged as a page view.

4. Report errors Next.js catches

Next.js wraps each route segment in an error boundary. An error caught there shows your error.tsx instead of reaching the window, so the SDK doesn’t see it. Report it from the file:

app/error.tsx
'use client';

import { useEffect } from 'react';
import { getLogger } from '@/lib/apperio';

export default function ErrorPage({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    getLogger()?.error('Page error', error, { digest: error.digest });
  }, [error]);

  return (
    <div>
      <p>Something went wrong.</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

Errors in the root layout itself are handled by app/global-error.tsx, which replaces the whole page, so it renders its own html and body:

app/global-error.tsx
'use client';

import { useEffect } from 'react';
import { getLogger } from '@/lib/apperio';

export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
  useEffect(() => {
    getLogger()?.fatal('Root layout error', error, { digest: error.digest });
  }, [error]);

  return (
    <html lang="en">
      <body>
        <p>Something went wrong.</p>
      </body>
    </html>
  );
}

Errors from server components

In production, an error thrown while rendering a server component reaches the browser with a generic message and a digest, not the real message. Both files above still report it with the digest; log the real error on the server, as in the next step, to see the details.

5. Log on the server

Use a separate logger on the server. Keep it on globalThis so hot reloading in development doesn’t create a new one on every change:

lib/apperio-server.ts
import { Apperio } from 'apperio';

const globalForApperio = globalThis as typeof globalThis & { apperioServer?: Apperio };

/** The server logger, created on first use. Returns null without credentials. */
export function getServerLogger(): Apperio | null {
  if (globalForApperio.apperioServer) return globalForApperio.apperioServer;

  const apiKey = process.env.APPERIO_API_KEY;
  const projectId = process.env.APPERIO_PROJECT_ID;
  if (!apiKey || !projectId) return null;

  globalForApperio.apperioServer = new Apperio({
    apiKey,
    projectId,
    environment: process.env.NODE_ENV,
    serviceName: 'web-server',
  });
  return globalForApperio.apperioServer;
}

In a route handler, flush before the response is sent. On serverless hosting the function may be frozen as soon as it returns, before a batch goes out:

app/api/orders/route.ts
import { getServerLogger } from '@/lib/apperio-server';

export async function POST(request: Request) {
  const logger = getServerLogger();
  try {
    const { orderId } = (await request.json()) as { orderId: string };
    await processOrder(orderId);
    logger?.info('Order placed', { orderId });
    return Response.json({ ok: true });
  } catch (err) {
    logger?.error('Order failed', err instanceof Error ? err : new Error(String(err)));
    return Response.json({ error: 'Order failed' }, { status: 500 });
  } finally {
    await logger?.flush();
  }
}

Errors on the server are never captured automatically; report them as above. See Node.js for more.

Node.js runtime only

Use the server logger in the Node.js runtime, the default for route handlers and server components. When it starts on a server, the SDK registers Node.js process handlers, which the Edge runtime and middleware don’t provide.

Notes

  • The browser logger starts after the page hydrates, so an error thrown during hydration itself isn’t captured by the SDK. error.tsx still reports rendering errors.
  • If you run next start yourself, note that the SDK exits the process after sending its logs on SIGTERM. See Shutdown.
  • Session replay needs no extra code: turn it on in the dashboard. See Session replay.