Star us on GitHub
Star
Menu

Next.js Walkthrough

Overview

Our Next.js SDK gives you access to frontend session replays and server-side monitoring, all-in-one.

  • On the frontend, the <HighlightInit/> component sets up client-side session replays.
  • On the backend, the withHighlight wrapper captures server-side errors and logs from Page Router API endpoints.
  • On the backend, instrumentation.ts and registerHighlight capture Page Router SSR errors and App Router API endpoint errors.
  • The withHighlightConfig configuration wrapper automatically proxies Highlight data to bypass ad-blockers and uploads source maps so your frontend errors include stack traces to your source code.
How Highlight captures Next.js errors
Page RouterApp Router
API ErrorswithHighlightinstrumentation.ts
SSR Errorsinstrumentation.tserror.tsx
Client<HighlightInit /><HighlightInit />
Installation
# with yarn yarn add @highlight-run/next
Copy
Environment Configuration (optional)

This section is extra opinionated about Next.js constants. It's not for everyone. We like how zod and TypeScript work together to validate process.env inputs... but this is a suggestion. Do your own thing and replace our imports (import CONSTANTS from '@/app/constants') with your own!

  • Install Zod: yarn add zod
  • Edit .env to add your projectID to NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID
# .env NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID='1jdkoe52'
Copy
  • Feed your environment variables into the application with a constants file. We're using zod for this example, because it creates a validated, typed CONSTANTS object that plays nicely with TypeScript.
// src/app/constants.ts import { z } from 'zod' // Must assign NEXT_PUBLIC_* env vars to a variable to force Next to inline them const publicEnv = { NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID: process.env.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID, } const CONSTANTS = z .object({ NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID: z.string(), }) .parse(publicEnv) export default CONSTANTS
Copy
Client Instrumentation

This sections adds session replay and frontend error monitoring to Highlight. This implementation requires React 17 or greater. If you're behind on React versions, follow our React.js docs

  • For the /pages directory, you'll want to add HighlightInit to _app.tsx.
// pages/_app.tsx import { AppProps } from 'next/app' import CONSTANTS from '@/app/constants' import { HighlightInit } from '@highlight-run/next/client' export default function MyApp({ Component, pageProps }: AppProps) { return ( <> <HighlightInit excludedHostnames={['localhost']} projectId={CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID} serviceName="my-nextjs-frontend" tracingOrigins networkRecording={{ enabled: true, recordHeadersAndBody: true }} /> <Component {...pageProps} /> </> ) }
Copy
  • For Next.js 13 App Directory, add HighlightInit to your layout.tsx file.
// src/app/layout.tsx import './globals.css' import CONSTANTS from '@/app/constants' import { HighlightInit } from '@highlight-run/next/client' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <> <HighlightInit excludedHostnames={['localhost']} projectId={CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID} serviceName="my-nextjs-frontend" tracingOrigins networkRecording={{ enabled: true, recordHeadersAndBody: true }} /> <html lang="en"> <body>{children}</body> </html> </> ) }
Copy
// src/app/components/error-boundary.tsx 'use client' import { ErrorBoundary as HighlightErrorBoundary } from '@highlight-run/next/client' export function ErrorBoundary({ children }: { children: React.ReactNode }) { const isLocalhost = typeof window === 'object' && window.location.host === 'localhost' return ( <HighlightErrorBoundary showDialog={!isLocalhost}> {children} </HighlightErrorBoundary> ) }
Copy
Skip Localhost tracking

We do not recommend enabling this while integrating Highlight for the first time because it will prevent you from validating that your local build can send data to Highlight.

In the case that you don't want local sessions being shipped to Highlight The excludedHostnames prop accepts an array of partial or full hostnames. For example, if you pass in excludedHostnames={['localhost', 'staging]}, you'll block localhost on all ports, www.staging.highlight.io and staging.highlight.com.

Alternatively, you could manually call H.start() and H.stop() to manage invocation on your own.

// src/app/layout.tsx <HighlightInit manualStart projectId={CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID} serviceName="my-nextjs-frontend" /> <CustomHighlightStart /> // src/app/custom-highlight-start.tsx 'use client' import { H } from '@highlight-run/next/client' export function CustomHighlightStart() { useEffect(() => { const shouldStartHighlight = window.location.hostname === 'https://www.highlight.io' if (shouldStartHighlight) { H.start(); return () => { H.stop() } } }) return null }
Copy
API Route Instrumentation (Page Router)

This section applies to Next.js Page Router routes only. Each Page Router route must be wrapped individually. We'll address App Router routes later in this walkthrough. Look for instrumentation.ts

  • Create a file to export your Highlight wrapper function:
// src/app/utils/highlight.config.ts: import CONSTANTS from '@/app/constants' import { Highlight } from '@highlight-run/next/server' export const withHighlight = Highlight({ projectID: '<YOUR_PROJECT_ID>', })
Copy
  • Wrap your /pages/api functions with withHighlight:
// pages/api/test.ts import { NextApiRequest, NextApiResponse } from 'next' import { withHighlight } from '@/app/utils/highlight.config' export default withHighlight(function handler( req: NextApiRequest, res: NextApiResponse, ) { console.info('Here: /api/app-directory-test', { success }) if (Math.random() < 0.8) { res.send('Success: /api/app-directory-test') } else { throw new Error('Error: /api/app-directory-test') } })
Copy
Server Instrumentation (App Router)

This section applies to Next.js App Router routes only (Next 13+). Excluding the Vercel edge runtime (which is a work in progress) and edge function logs, this section will cover all of your error monitoring and logging needs for NextJS. If you do require those logs, we recommend the Vercel Log Drain.

This section adds server-side error monitoring and log capture to Highlight.

Next.js comes out of the box instrumented for Open Telemetry. Our example Highlight implementation will use Next's experimental instrumentation feature to configure Open Telemetry on our Next.js server. There are probably other ways to configure Open Telemetry with Next, but this is our favorite.

  • Create instrumentation.ts at the root of your project as explained in the instrumentation guide. Call registerHighlight from within the exported register function.
// instrumentation.ts import CONSTANTS from '@/app/constants' export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { /** Conditional import required for use with Next middleware to avoid a webpack error * https://nextjs.org/docs/pages/building-your-application/routing/middleware */ const { registerHighlight } = await import('@highlight-run/next/server') registerHighlight({ projectID: CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID, }) } }
Copy
  • If you're using the App Router, copy instrumentation.ts to src/instrumentation.ts. See this Next.js discussion regarding instrumentation.ts with App Router. You could also simply export the register function from instrumentation.ts in src/instrumentation.ts like so:
// src/instrumentation.ts: export { register } from '../instrumentation'
Copy
error.tsx (App Router)

instrumentation.ts does not catch SSR errors from the App Router. App Router instead uses error.tsx to send server-side rendering errors to the client. We can catch and consume those error with a custom error page.

These errors will display as client errors, even though we know that they're server errors.

// src/app/error.tsx 'use client' // Error components must be Client Components import { H } from '@highlight-run/next/client' import { useEffect } from 'react' export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { useEffect(() => { // Log the error to Highlight H.consumeError(error) }, [error]) return ( <div> <h2>Something went wrong!</h2> <button onClick={ // Attempt to recover by trying to re-render the segment () => reset() } > Try again </button> </div> ) }
Copy
Private Sourcemaps and Request Proxying (optional)

Adding the withHighlightConfig to your next config will configure highlight frontend proxying. This means that frontend session recording and error capture data will be piped through your domain on /highlight-events to avoid ad-blockers from stopping this traffic.

  • Turn on instrumentationHook.
  • Wrap the config with withHighlightConfig.

If you use a next.config.js file:

// next.config.js const { withHighlightConfig } = require('@highlight-run/next/server') /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { appDir: true, instrumentationHook: true, }, productionBrowserSourceMaps: true, } module.exports = withHighlightConfig(nextConfig)
Copy

If you use a next.config.mjs file:

// next.config.mjs import { dirname } from 'path' import { fileURLToPath } from 'url' import { withHighlightConfig } from '@highlight-run/next/server' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) /** @type {import('next').NextConfig} */ const nextConfig = withHighlightConfig({ experimental: { appDir: true, instrumentationHook: true, }, productionBrowserSourceMaps: true, }) export default nextConfig
Copy
Configure inlineImages

We use a package called rrweb to record web sessions. rrweb supports inlining images into sessions to improve replay accuracy, so that images that are only available from your local network can be saved; however, the inlined images can cause CORS issues in some situations.

We currently default inlineImages to true on localhost. Explicitly set inlineImages={false} if you run into trouble loading images on your page while Highlight is running. This will degrade tracking on localhost and other domains that are inaccessible to app.highlight.io.

Configure tracingOrigins and networkRecording

See Fullstack Mapping for details.

You likely want to associate your back-end errors to client sessions.

Source Map Validation

Source maps work differently in development mode than in production. Run yarn build && yarn start to test compiled source maps in Highlight.

We recommend shipping your source maps to your production server. Your client-side JavaScript is always public, and code decompilation tools are so powerful that obscuring your source code may not be helpful.

Shipping source maps to production with Next.js is as easy as setting productionBrowserSourceMaps: true in your nextConfig. Alternatively, you can upload source maps directly to Highlight using our withHighlightConfig function.

// next.config.js const { withHighlightConfig } = require('@highlight-run/next/server') /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { appDir: true, instrumentationHook: true, }, productionBrowserSourceMaps: false } module.exports = withHighlightConfig(nextConfig)
Copy

You must export your HIGHLIGHT_SOURCEMAP_UPLOAD_API_KEY to your build process. If you're building and deploying with Vercel, try our Highlight Vercel Integration to inject HIGHLIGHT_SOURCEMAP_UPLOAD_API_KEY automatically.

Vercel Log Drain

Vercel Log Drain works great. Install our Vercel + Highlight Integration to enable it.