Next.js Walkthrough
Limitations
⚠️ App Directory support on Vercel is a work-in-progress. Session Replay, Vercel Log Drain and non-Vercel-deployed Error Monitoring are fully operational; however, Vercel's edge
runtime is not yet compatible with OpenTelemetry. Vercel's nodejs
runtime is instrumented for OpenTelemetry, but we're having trouble ingesting Server-side Rendering (SSR) errors. We expect to solve the nodejs
runtime issues first. In the meantime, we're hoping to find an edge
runtime-compatible version of OpenTelemetry.
⚠️ Source maps do not work in development mode. Run yarn build && yarn start
to test compiled source maps in Highlight.
Installation
# with npm npm install @highlight-run/next @highlight-run/react highlight.run
# with yarn yarn add @highlight-run/next @highlight-run/react highlight.run
Environment Configuration (Very optional)
This section is extra opinionated about Next.js constants. It's not for everyone. We like how
zod
and TypeScript work together to validateprocess.env
inputs... but this is a suggestion. Do your own thing!
- Edit
.env
to add your projectID toNEXT_PUBLIC_HIGHLIGHT_PROJECT_ID
- To send data to a locally-running instance of Highlight, create
.env.local
at your project root with variables for your localotlpEndpoint
andbackendUrl
:
# .env.local NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID='1jdkoe52' # omit to send data to app.highlight.io NEXT_PUBLIC_HIGHLIGHT_OTLP_ENDPOINT='http://localhost:4318' NEXT_PUBLIC_HIGHLIGHT_BACKEND_URL='https://localhost:8082/public'
- Feed your environment variables into the application with a constants file. We're using
zod
for this example, because it creates a validated, typedCONSTANTS
object that plays nicely with TypeScript.
// src/app/constants.ts import { z } from 'zod' const stringOrUndefined = z.preprocess( (val) => val || undefined, z.string().optional(), ) // 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, NEXT_PUBLIC_HIGHLIGHT_OTLP_ENDPOINT: process.env.NEXT_PUBLIC_HIGHLIGHT_OTLP_ENDPOINT, NEXT_PUBLIC_HIGHLIGHT_BACKEND_URL: process.env.NEXT_PUBLIC_HIGHLIGHT_BACKEND_URL, } const CONSTANTS = z .object({ NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID: z.string(), NEXT_PUBLIC_HIGHLIGHT_OTLP_ENDPOINT: stringOrUndefined, NEXT_PUBLIC_HIGHLIGHT_BACKEND_URL: stringOrUndefined, }) .parse(publicEnv) export default CONSTANTS
Instrument your API routes
- 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' if (process.env.NODE_ENV === 'development') { // Highlight's dev instance expects HTTPS. Disable HTTPS errors in development. process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' } export const withHighlight = Highlight({ projectID: CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID, otlpEndpoint: CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_OTLP_ENDPOINT, backendUrl: CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_BACKEND_URL, })
- Wrap your
/pages/api
functions withwithHighlight
:
// pages/api/test.ts import { NextApiRequest, NextApiResponse } from 'next' import { withHighlight } from '@/app/utils/highlight.config' import { z } from 'zod' export default withHighlight(function handler( req: NextApiRequest, res: NextApiResponse, ) { const success = z.enum(['true', 'false']).parse(req.query.success) console.info('Here: /api/app-directory-test', { success }) if (success === 'true') { res.send('Success: /api/app-directory-test') } else { throw new Error('Error: /api/app-directory-test') } })
Instrument the server
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.
- Install
next-build-id
withnpm install next-build-id
.
- Turn on
instrumentationHook
. We've also turned onproductionBrowserSourceMaps
because Highlight is much easier to use with source maps.
// next.config.js const nextBuildId = require('next-build-id') /** @type {import('next').NextConfig} */ const nextConfig = { generateBuildId: () => nextBuildId({ dir: __dirname }), experimental: { appDir: true, instrumentationHook: true, }, productionBrowserSourceMaps: true, } module.exports = nextConfig
You may have trouble with a missing __dirname
environment variable. This can happen with Next.js middleware. The following example uses a next.config.mjs
file instead of the CJS-style next.config.js
pattern. It calculates __dirname
using native Node.js utility packages.
// next.config.mjs import { dirname } from 'path' import { fileURLToPath } from 'url' import nextBuildId from 'next-build-id' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) /** @type {import('next').NextConfig} */ const nextConfig = { generateBuildId: () => nextBuildId({ dir: __dirname }), experimental: { appDir: true, instrumentationHook: true, }, productionBrowserSourceMaps: true, } export default nextConfig
- Create
instrumentation.ts
at the root of your project as explained in the instrumentation guide. CallregisterHighlight
from within the exportedregister
function:
// instrumentation.ts import CONSTANTS from '@/app/constants' import { registerHighlight } from '@highlight-run/next' export async function register() { registerHighlight({ projectID: CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID, otlpEndpoint: CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_OTLP_ENDPOINT, }) }
You'll need to do a conditional import if you're using Next Middleware.
// 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 * * Avoids the following error: * An error occurred while loading instrumentation hook: (0 , _highlight_run_next__WEBPACK_IMPORTED_MODULE_1__.registerHighlight) is not a function */ const { registerHighlight } = await import('@highlight-run/next') registerHighlight({ projectID: CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID, otlpEndpoint: CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_OTLP_ENDPOINT, }) } }
Instrument the client
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 addHighlightInit
to_app.tsx
.
// pages/_app.tsx import { AppProps } from 'next/app' import CONSTANTS from '@/app/constants' import { HighlightInit } from '@highlight-run/next/highlight-init' export default function MyApp({ Component, pageProps }: AppProps) { return ( <> <HighlightInit projectId={CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID} tracingOrigins networkRecording={{ enabled: true, recordHeadersAndBody: true, urlBlocklist: [], }} backendUrl={CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_BACKEND_URL} /> <Component {...pageProps} /> </> ) }
- For App Directory, add
HighlightInit
to yourlayout.tsx
file.
// src/app/layout.tsx import './globals.css' import CONSTANTS from '@/app/constants' import { HighlightInit } from '@highlight-run/next/highlight-init' export const metadata = { title: 'Highlight Next Demo', description: 'Check out how Highlight works with Next.js', } export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <> <HighlightInit projectId={CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID} tracingOrigins networkRecording={{ enabled: true, recordHeadersAndBody: true, urlBlocklist: [], }} backendUrl={CONSTANTS.NEXT_PUBLIC_HIGHLIGHT_BACKEND_URL} /> <html lang="en"> <body>{children}</body> </html> </> ) }
Configure tracingOrigins
and networkRecording
See Fullstack Mapping for details.
You likely want to associate your back-end errors to client sessions.
Test source maps
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.
Make sure to implement nextConfig.generateBuildId
so that our source map uploader can version your source maps correctly. Make sure to omit productionBrowserSourceMaps
or set it to false to enable the source map uploader.
// next.config.js const nextBuildId = require('next-build-id') const { withHighlightConfig } = require('@highlight-run/next') /** @type {import('next').NextConfig} */ const nextConfig = { generateBuildId: () => nextBuildId({ dir: __dirname }), experimental: { appDir: true, instrumentationHook: true, }, productionBrowserSourceMaps: false } module.exports = withHighlightConfig(nextConfig)
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.