Next.js Integration Coming Soon
Coming Soon
The @nadi-pro/nextjs package is currently under development. The documentation below describes the planned API and features.
Integrate Nadi with Next.js applications for comprehensive error tracking across client and server.
Installation
bash
npm install @nadi-pro/browser @nadi-pro/nextjsSetup
Configuration File
Create nadi.client.config.ts:
typescript
// nadi.client.config.ts
import { init } from '@nadi-pro/browser'
init({
appKey: process.env.NEXT_PUBLIC_NADI_APP_KEY!,
environment: process.env.NODE_ENV,
release: process.env.NEXT_PUBLIC_VERSION,
enableWebVitals: true,
})Create nadi.server.config.ts:
typescript
// nadi.server.config.ts
import { init } from '@nadi-pro/node'
init({
apiKey: process.env.NADI_API_KEY!,
appKey: process.env.NADI_APP_KEY!,
environment: process.env.NODE_ENV,
release: process.env.NEXT_PUBLIC_VERSION,
})Next.js Config
javascript
// next.config.js
const { withNadi } = require('@nadi-pro/nextjs')
module.exports = withNadi({
// Your Next.js config
})({
nadi: {
hideSourceMaps: true,
},
})App Router (Next.js 13+)
typescript
// app/layout.tsx
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
// app/error.tsx (client error boundary)
'use client'
import { useEffect } from 'react'
import { captureException } from '@nadi-pro/browser'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
captureException(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
// app/global-error.tsx (root error boundary)
'use client'
import { useEffect } from 'react'
import { captureException } from '@nadi-pro/browser'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
captureException(error)
}, [error])
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}Pages Router
typescript
// pages/_app.tsx
import type { AppProps } from 'next/app'
import '../nadi.client.config'
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
// pages/_error.tsx
import { captureException } from '@nadi-pro/browser'
import type { NextPage, NextPageContext } from 'next'
interface ErrorProps {
statusCode?: number
}
const Error: NextPage<ErrorProps> = ({ statusCode }) => {
return (
<div>
<h1>
{statusCode
? `An error ${statusCode} occurred on server`
: 'An error occurred on client'}
</h1>
</div>
)
}
Error.getInitialProps = async ({ res, err }: NextPageContext) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
if (err) {
captureException(err)
}
return { statusCode }
}
export default ErrorServer Components
Error Handling
typescript
// app/dashboard/page.tsx
import { captureException } from '@nadi-pro/node'
async function getData() {
try {
const res = await fetch('https://api.example.com/data')
if (!res.ok) throw new Error('Failed to fetch')
return res.json()
} catch (error) {
captureException(error)
throw error
}
}
export default async function Dashboard() {
const data = await getData()
return <div>{/* render data */}</div>
}Route Handlers
typescript
// app/api/users/route.ts
import { NextResponse } from 'next/server'
import { captureException } from '@nadi-pro/node'
export async function GET() {
try {
const users = await getUsers()
return NextResponse.json(users)
} catch (error) {
captureException(error)
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
)
}
}Client Components
Error Boundary
typescript
// components/ErrorBoundary.tsx
'use client'
import { Component, ReactNode } from 'react'
import { captureException } from '@nadi-pro/browser'
interface Props {
children: ReactNode
fallback?: ReactNode
}
interface State {
hasError: boolean
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(): State {
return { hasError: true }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
captureException(error, {
extra: {
componentStack: errorInfo.componentStack,
},
})
}
render(): ReactNode {
if (this.state.hasError) {
return this.props.fallback || <div>Something went wrong</div>
}
return this.props.children
}
}Event Handlers
typescript
'use client'
import { captureException, addBreadcrumb } from '@nadi-pro/browser'
export function CheckoutButton() {
const handleClick = async () => {
addBreadcrumb({
category: 'ui',
message: 'Checkout button clicked',
})
try {
await processCheckout()
} catch (error) {
captureException(error)
}
}
return <button onClick={handleClick}>Checkout</button>
}Middleware
typescript
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Add request ID for tracking
const requestId = crypto.randomUUID()
const response = NextResponse.next()
response.headers.set('x-request-id', requestId)
return response
}API Routes (Pages Router)
typescript
// pages/api/data.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import { captureException, withScope } from '@nadi-pro/node'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const data = await fetchData()
res.status(200).json(data)
} catch (error) {
withScope((scope) => {
scope.setTag('api', 'data')
scope.setExtra('query', req.query)
captureException(error)
})
res.status(500).json({ error: 'Internal Server Error' })
}
}Source Maps
Automatic Upload
javascript
// next.config.js
const { withNadi } = require('@nadi-pro/nextjs')
module.exports = withNadi({
nadi: {
// Upload source maps during build
uploadSourceMaps: true,
// Delete source maps after upload
hideSourceMaps: true,
// Release version
release: process.env.NEXT_PUBLIC_VERSION,
},
})()Environment Variables
bash
# .env.local
NADI_API_KEY=your-api-key
NADI_APP_KEY=your-app-key
NEXT_PUBLIC_NADI_APP_KEY=your-app-key
NEXT_PUBLIC_VERSION=1.0.0User Context
typescript
// app/providers.tsx
'use client'
import { useEffect } from 'react'
import { setUser } from '@nadi-pro/browser'
import { useSession } from 'next-auth/react'
export function NadiUserProvider({ children }: { children: React.ReactNode }) {
const { data: session } = useSession()
useEffect(() => {
if (session?.user) {
setUser({
id: session.user.id,
email: session.user.email!,
name: session.user.name!,
})
} else {
setUser(null)
}
}, [session])
return <>{children}</>
}Web Vitals
typescript
// app/layout.tsx
import { WebVitals } from './web-vitals'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<WebVitals />
{children}
</body>
</html>
)
}
// app/web-vitals.tsx
'use client'
import { useReportWebVitals } from 'next/web-vitals'
import { recordMetric } from '@nadi-pro/browser'
export function WebVitals() {
useReportWebVitals((metric) => {
recordMetric({
name: metric.name,
value: metric.value,
tags: {
path: window.location.pathname,
},
})
})
return null
}Testing
Mock SDK
typescript
// __mocks__/@nadi-pro/browser.ts
export const init = jest.fn()
export const captureException = jest.fn()
export const captureMessage = jest.fn()
export const setUser = jest.fn()
export const addBreadcrumb = jest.fn()Test Setup
typescript
// jest.setup.ts
jest.mock('@nadi-pro/browser')Nuxt.js Alternative
For Nuxt.js, use a similar approach:
typescript
// plugins/nadi.client.ts
import { init } from '@nadi-pro/browser'
export default defineNuxtPlugin(() => {
init({
appKey: useRuntimeConfig().public.nadiAppKey,
environment: process.env.NODE_ENV,
})
})Best Practices
Do
- Use separate configs for client/server
- Upload source maps in CI/CD
- Track Web Vitals
- Set user context after auth
Don't
- Expose API keys to client
- Forget server-side error handling
- Skip error boundaries
- Miss API route errors
Troubleshooting
Client Errors Not Captured
- Verify
nadi.client.config.tsis imported - Check
NEXT_PUBLIC_NADI_APP_KEYis set - Ensure error boundaries are in place
Server Errors Not Captured
- Verify
nadi.server.config.tsis imported - Check
NADI_API_KEYis set - Ensure try/catch in API routes
Source Maps Not Working
- Check
uploadSourceMaps: truein config - Verify API key has upload permissions
- Check release version matches
Next Steps
- React Integration - React-specific features
- Source Maps - Source map configuration
- Web Vitals - Performance monitoring