# Better Stack Next.js client

## Start logging in 6 minutes

### 1. Install

Install Logtail Next.js NPM package:

```bash
[label Install Logtail Next.js]
npm install @logtail/next
```

[info]
#### Using the Pages Router?
See [legacy docs](https://betterstack.com/docs/logs/javascript/nextjs-legacy/) for `v0.1.*`, or [upgrade to the App Router](#upgrade-to-the-app-router).
[/info]


### 2. Setup

Set environment variables, e.g. in your console before running the server:

```bash
[label Set environment variable]
export NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN="$SOURCE_TOKEN"
export NEXT_PUBLIC_BETTER_STACK_INGESTING_URL="https://$INGESTING_HOST"
```

Set up Better Stack Next.js client in `next.config.ts` or `next.config.js`:

```ts
[label Wrap Next.js config with Better Stack]
import { withBetterStack } from '@logtail/next';

export default withBetterStack({
  // Your existing config
});
```

### 3. Start logging 🎉

Use structured logging in client, edge, or server-side files.

You should see your logs in [Better Stack → Live tail](https://telemetry.betterstack.com/team/0/tail ";_blank").

[info]
#### Using older versions of Next.js?

For Next.js version 14, use `@logtail/next` in `0.2.*`. For slight differences in syntax, see the "in Next.js v14" tab in examples below.

Older versions 12.1.4 and above are supported in `@logtail/next` version `0.1.*`. See [legacy docs](https://betterstack.com/docs/logs/javascript/nextjs-legacy/).
[/info]

### Route handlers

Wrap your route handlers in `withBetterStack` to add a logger to your request and log exceptions automatically:

[code-tabs]
```ts
[label Wrap route handler with Better Stack]
import { withBetterStack, BetterStackRequest } from '@logtail/next';

export const GET = withBetterStack((request: BetterStackRequest) => {
  request.log.info('Login function called');

  // You can create intermediate loggers
  const log = request.log.with({ scope: 'user' });
  log.info('User logged in', { userId: 42 });

  return NextResponse.json({ hello: 'world' });
});
```
```ts
[label in Next.js v14]
import { withBetterStack, BetterStackRequest } from '@logtail/next';

export const GET = withBetterStack((req: BetterStackRequest) => {
  req.log.info('Login function called');

  // You can create intermediate loggers
  const log = req.log.with({ scope: 'user' });
  log.info('User logged in', { userId: 42 });

  return NextResponse.json({ hello: 'world' });
});
```
[/code-tabs]

### Client components

To send logs from client components, add `useLogger` to your component:

[code-tabs]
```ts
[label Send logs from client components to Better Stack]
'use client';
import { useLogger } from '@logtail/next/hooks';

export default function ClientComponent() {
  const log = useLogger();
  log.debug('User logged in', { userId: 42 });
  return <h1>Logged in</h1>;
}
```
```ts
[label in Next.js v14]
'use client';
import { useLogger } from '@logtail/next';

export default function ClientComponent() {
  const log = useLogger();
  log.debug('User logged in', { userId: 42 });
  return <h1>Logged in</h1>;
}
```
[/code-tabs]

### Server components

To send logs from server components, add `Logger` to your component, and call flush before returning:

```ts
[label Send logs from server components to Better Stack]
import { Logger } from '@logtail/next';

export default async function ServerComponent() {
  const log = new Logger();
  log.info('User logged in', { userId: 42 });

  // ...

  await log.flush();
  return <h1>Logged in</h1>;
}
```

### Configuring authentication middleware

If your Next.js application uses authentication middleware (like Clerk or NextAuth.js), it may block telemetry requests sent from the browser. These requests, sent to the `/_betterstack/*` proxy route, are essential for collecting [client-side logs](#client-components) and [Web Vitals](#web-vitals).

To prevent issues, you must configure your middleware to treat `/_betterstack/(.*)` as a public or ignored route.

Here are configuration examples for common middleware libraries:

[code-tabs]
```ts
[label middleware.ts (Clerk)]
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";

const isPublicRoute = createRouteMatcher([
  '/sign-in(.*)',
  '/sign-up(.*)',
  '/_betterstack/(.*)', // Allow Better Stack telemetry
]);

export default clerkMiddleware((auth, request) => {
  // Skip authentication for public routes
  if (isPublicRoute(request)) {
    return;
  }

  // Protect all other routes
  auth().protect();
});

export const config = {
  matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
```
```ts
[label middleware.ts (NextAuth.js)]
export { default } from "next-auth/middleware"

export const config = { 
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * - _betterstack (Better Stack telemetry)
     */
    '/((?!api|_next/static|_next/image|favicon.ico|_betterstack).*)',
  ]
}
```
[/code-tabs]

By correctly configuring your middleware, you ensure that all telemetry data from the browser is successfully collected.

### Keep telemetry failures out of your app

By default, `withBetterStack` proxies browser telemetry to Better Stack using a Next.js rewrite. If the ingest endpoint is slow or unreachable, Next.js turns the failed rewrite into a `500` error on your own domain.

To make delivery failures invisible to the browser, serve the proxy path with the route handler included in `@logtail/next` version `0.4.0` or higher. It forwards telemetry with a 5-second timeout and always responds with `204`, logging delivery failures on the server only.

Pick a proxy path that can host a route handler. The default `/_betterstack` can't, as folders prefixed with `_` are private in the App Router:

```bash
[label Set the proxy path in .env]
NEXT_PUBLIC_BETTER_STACK_PROXY_PATH=/betterstack
```

Export the route handler for both proxied endpoints:

```ts
[label app/betterstack/logs/route.ts]
// Create the same file in app/betterstack/web-vitals/route.ts
export { POST } from '@logtail/next/proxy';
```

Static route files take precedence over the rewrite, so nothing else changes. Use one file per endpoint, as a `[...path]` catch-all would be shadowed by the rewrite.

Using authentication middleware? Allow your new proxy path instead of `/_betterstack` in the [middleware configuration](#configuring-authentication-middleware).

## Need help?

Please let us know at hello@betterstack.com.  
We're happy to help! 🙏

## Advanced usage

### Capture traffic requests

To capture traffic requests, create a `middleware.ts` file in the root folder of your Next.js app:

```ts
[label Capture traffic requests using middleware]
import { Logger } from '@logtail/next'
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'

export async function middleware(request: NextRequest, event: NextFetchEvent) {
  const logger = new Logger({ source: "middleware" });
  await logger.middleware(request, { logRequestDetails: ["method", "nextUrl"] });

  event.waitUntil(logger.flush());
  return NextResponse.next();
}
```

[info]
#### Looking for the request body?
Middleware leaves the request body untouched in `@logtail/next` version `0.5.0` or higher, so that it reaches your route handler intact. Log the body from the route handler instead, using `withBetterStack(handler, { logRequestDetails: true })`.
[/info]

### Redact sensitive data

Keep secrets out of your logs using the `redact` option in `@logtail/next` version `0.5.1` or higher. Values of matching keys are replaced with `[FILTERED]` in log fields and request details:

```ts
[label Redact sensitive data in logs]
import { Logger } from '@logtail/next';

const log = new Logger({ redact: ['password', 'authorization', /^x-api-/i] });

// Sends { "user": "ada", "password": "[FILTERED]" }
log.info('User signed in', { user: 'ada', password: 'hunter2' });
```

A string matches every key that contains it, ignoring case. A regular expression is tested against the key.

Route handlers accept the same option:

```ts
[label Redact request details in route handlers]
import { withBetterStack, BetterStackRequest } from '@logtail/next';

export const POST = withBetterStack(
  async (request: BetterStackRequest) => {
    // ...
  },
  { logRequestDetails: true, redact: ['password', 'authorization', 'cookie'] },
);
```

### Log levels

The log level defines the lowest level of logs sent to Better Stack. Choose one of the following levels (from lowest to highest):

- `debug` is the default setting. It means that you send all logs to Better Stack.
- `info`
- `warn`
- `error` means that you only send the highest-level logs to Better Stack.
- `off` means that you don't send any logs to Better Stack.

For example, to send all logs except for debug logs to Better Stack:

```sh
[label Set environment variable to change log level]
export NEXT_PUBLIC_BETTER_STACK_LOG_LEVEL=info
```

### Retry failed requests

Logs that fail to reach Better Stack are sent again in `@logtail/next` version `0.5.0` or higher. By default, a request is retried 3 times, waiting 100 milliseconds before each retry. Network errors and responses with status `429` or `5xx` are retried.

Change the defaults when creating a logger:

```ts
[label Configure retries]
import { Logger } from '@logtail/next';

const log = new Logger({ retryCount: 5, retryBackoff: 500 });
```

Set `retryCount` to `0` to send every request only once.

### Web Vitals

To send Web Vitals to Better Stack, add the `BetterStackWebVitals` component to the `app/layout.tsx` file:

[code-tabs]
```ts
[label Send Web Vitals to Better Stack]
import { BetterStackWebVitals } from '@logtail/next/webVitals';

export default function RootLayout() {
  return (
    <html>
      ...
      <BetterStackWebVitals />
      <div>...</div>
    </html>
  );
}
```
```ts
[label in Next.js v14]
import { BetterStackWebVitals } from '@logtail/next';

export default function RootLayout() {
  return (
    <html>
      ...
      <BetterStackWebVitals />
      <div>...</div>
    </html>
  );
}
```
[/code-tabs]

### Capture errors

To capture routing errors, use the [error handling mechanism of Next.js](https://nextjs.org/docs/app/building-your-application/routing/error-handling):

1. Go to the `app` folder.
2. Create an `error.tsx` file.
3. Inside your component function, add `useLogger` to send the error to Better Stack. For example:

[code-tabs]
```ts
[label Send errors to Better Stack]
"use client";

import { useLogger } from "@logtail/next/hooks";
import { LogLevel } from "@logtail/next";
import { usePathname } from "next/navigation";

export default function ErrorPage({
  error,
}: {
  error: Error & { digest?: string };
}) {
  const pathname = usePathname()
  const log = useLogger({ source: "error.tsx" });
  let status =  error.message == 'Invalid URL' ? 404 : 500;

  log.logHttpRequest(
    LogLevel.error,
    error.message,
    {
      host: window.location.href,
      path: pathname,
      statusCode: status,
    },
    {
      error: error.name,
      cause: error.cause,
      stack: error.stack,
      digest: error.digest,
    },
  );

  return (
    <div className="p-8">
      Ops! An Error has occurred:{" "}
      <p className="text-red-400 px-8 py-2 text-lg">`{error.message}`</p>
      <div className="w-1/3 mt-8">
        <NavTable />
      </div>
    </div>
  );
}
```
```ts
[label in Next.js v14]
"use client";

import { useLogger, LogLevel } from "@logtail/next";
import { usePathname } from "next/navigation";

export default function ErrorPage({
  error,
}: {
  error: Error & { digest?: string };
}) {
  const pathname = usePathname()
  const log = useLogger({ source: "error.tsx" });
  let status =  error.message == 'Invalid URL' ? 404 : 500;

  log.logHttpRequest(
    LogLevel.error,
    error.message,
    {
      host: window.location.href,
      path: pathname,
      statusCode: status,
    },
    {
      error: error.name,
      cause: error.cause,
      stack: error.stack,
      digest: error.digest,
    },
  );

  return (
    <div className="p-8">
      Ops! An Error has occurred:{" "}
      <p className="text-red-400 px-8 py-2 text-lg">`{error.message}`</p>
      <div className="w-1/3 mt-8">
        <NavTable />
      </div>
    </div>
  );
}
```
[/code-tabs]

### How can I extend the logger?

You can use `log.with` to create an intermediate logger, for example:

```typescript
[label Extend logger with additional context]
const logger = useLogger().with({ userId: 42 });
logger.info('Hi'); // will ingest { ..., "message": "Hi", "fields" { "userId": 42 }}
```

### Send traces and metrics using OpenTelemetry

Send traces, metrics, and logs from your Next.js app to Better Stack using [OpenTelemetry](https://opentelemetry.io/) with the [@vercel/otel](https://www.npmjs.com/package/@vercel/otel) package.

Install the packages:

```bash
[label Install OpenTelemetry packages]
npm install @vercel/otel@2 @opentelemetry/exporter-trace-otlp-http@0.223 @opentelemetry/exporter-metrics-otlp-http@0.223 @opentelemetry/exporter-logs-otlp-http@0.223 @opentelemetry/sdk-trace-node@2 @opentelemetry/sdk-metrics@2 @opentelemetry/sdk-logs@0.223
```

`@vercel/otel` 2.x requires the 2.x release line of the OpenTelemetry JavaScript SDK, so the command pins every OpenTelemetry package to that line.

Create an `instrumentation.ts` file in the root of your project, or in the `src` folder if you use one:

```ts
[label instrumentation.ts]
import { registerOTel } from '@vercel/otel';

export async function register() {
  // OTLP exporters run in the Node.js runtime only
  if (process.env.NEXT_RUNTIME !== 'nodejs') return;

  const { OTLPTraceExporter } = await import('@opentelemetry/exporter-trace-otlp-http');
  const { OTLPMetricExporter } = await import('@opentelemetry/exporter-metrics-otlp-http');
  const { OTLPLogExporter } = await import('@opentelemetry/exporter-logs-otlp-http');
  const { BatchSpanProcessor } = await import('@opentelemetry/sdk-trace-node');
  const { PeriodicExportingMetricReader } = await import('@opentelemetry/sdk-metrics');
  const { BatchLogRecordProcessor } = await import('@opentelemetry/sdk-logs');

  const headers = { Authorization: 'Bearer $SOURCE_TOKEN' };

  registerOTel({
    serviceName: 'your-nextjs-app',
    spanProcessors: [
      new BatchSpanProcessor(
        new OTLPTraceExporter({ url: 'https://$INGESTING_HOST/v1/traces', headers })
      ),
    ],
    metricReaders: [
      new PeriodicExportingMetricReader({
        exporter: new OTLPMetricExporter({ url: 'https://$INGESTING_HOST/v1/metrics', headers }),
      }),
    ],
    logRecordProcessors: [
      new BatchLogRecordProcessor({
        exporter: new OTLPLogExporter({ url: 'https://$INGESTING_HOST/v1/logs', headers }),
      }),
    ],
  });
}
```

Next.js runs `register` on server start. You should see your data in [Better Stack → Live tail](https://telemetry.betterstack.com/team/0/tail ";_blank").

For more instrumentation options, see our [OpenTelemetry guide](https://betterstack.com/docs/logs/open-telemetry/).

## Upgrade to the App Router

`@logtail/next` switched to support the App Router starting with version `0.2.0`.
If you are upgrading a Pages Router app with `@logtail/next` `v0.1.x` to the App Router, you will need to make the following changes:

- Upgrade `@logtail/next` to version `0.2.0` or higher
- Make sure that exported variables has `NEXT_PUBLIC_` prefix, e.g: `NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN`
- Use `useLogger` hook in client components instead of `log` prop
- For server side components, you will need to create an instance of `Logger` and flush the logs before component returns.
- For web-vitals, remove `reportWebVitals()` and instead add the `BetterStackWebVitals` component to your layout.
