Lightweight monitoring SDK for Next.js API routes and Server Actions.
Nurbak Watch monitors every Next.js API route and Server Action automatically — no external pings, no agents, no YAML. Health checks run inside your server process, catching issues that external monitors miss.
The fastest way to get started is with the CLI:
npx @nurbak/watch initThe CLI will automatically:
src/ directory, package manager)instrumentation.ts with the SDK initializedmiddleware.ts with the monitoring wrapper.env.localNon-interactive mode (for CI):
npx @nurbak/watch init --key nw_test_your_key_hereThat's it. Start your dev server and events will appear in your dashboard within 30 seconds.
# npm
npm install @nurbak/watch
# yarn
yarn add @nurbak/watch
# pnpm
pnpm add @nurbak/watch
# bun
bun add @nurbak/watchThis file hooks into the Next.js instrumentation API and initializes the SDK when your server starts.
App Router with src/ directory — create src/instrumentation.ts:
import { initWatch } from '@nurbak/watch'
export async function register() {
initWatch({
apiKey: process.env.NODE_ENV === 'production'
? process.env.NURBAK_WATCH_KEY_LIVE!
: process.env.NURBAK_WATCH_KEY_TEST!,
})
}Without src/ directory — create instrumentation.ts at the project root.
JavaScript projects — use the same code in instrumentation.js (remove the ! non-null assertions).
The middleware captures App Router API route requests, including latency, status codes, and response metadata.
import { withNurbakMiddleware } from '@nurbak/watch'
export default withNurbakMiddleware()
export const config = { matcher: '/api/:path*' }Add your keys to .env.local:
# For development
NURBAK_WATCH_KEY_TEST=nw_test_your_key_here
# For production
NURBAK_WATCH_KEY_LIVE=nw_live_your_key_hereIf you’re on Next.js 13.4–14.x, enable the experimental hook in next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
instrumentationHook: true,
},
}
module.exports = nextConfigNext.js 15+ has instrumentation enabled by default — no config change needed.
Pass options to initWatch() to customize the SDK:
import { initWatch } from '@nurbak/watch'
export async function register() {
initWatch({
apiKey: process.env.NURBAK_WATCH_KEY_LIVE!,
enabled: true,
debug: false,
sampleRate: 1.0,
ignorePaths: ['/api/health'],
flushInterval: 5000,
maxBatchSize: 100,
ingestUrl: 'https://ingestion.nurbak.com',
})
}| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Required. Your Nurbak Watch API key. |
enabled | boolean | true | Set to false to disable. Automatically disabled in NODE_ENV=test. |
debug | boolean | false | Enables verbose console logging. |
sampleRate | number | 1.0 | Fraction of requests to monitor (0.0–1.0). Useful for high-traffic apps. |
ignorePaths | string[] | [] | Path prefixes to exclude from monitoring. |
flushInterval | number | 5000 | Milliseconds between automatic queue flushes. |
maxBatchSize | number | 100 | Maximum events per batch. |
ingestUrl | string | https://ingestion.nurbak.com | Override for self-hosted or testing. |
| Variable | Description |
|---|---|
NURBAK_WATCH_KEY_LIVE | API key for production. Used when NODE_ENV=production. |
NURBAK_WATCH_KEY_TEST | API key for development/staging. |
NURBAK_WATCH_KEY | Fallback API key if the environment-specific key is not set. |
NURBAK_WATCH_DEBUG | Set to "true" to enable debug logging in the middleware layer. |
NURBAK_WATCH_INGEST_URL | Override the default ingest endpoint from the middleware layer. |
import { withNurbakMiddleware } from '@nurbak/watch'
export default withNurbakMiddleware()
export const config = { matcher: '/api/:path*' }import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { withNurbakMiddleware } from '@nurbak/watch'
function myMiddleware(request: NextRequest) {
const response = NextResponse.next()
response.headers.set('x-custom-header', 'value')
return response
}
export default withNurbakMiddleware(myMiddleware)
export const config = { matcher: '/api/:path*' }The wrapper passes the request through your middleware first, then captures the response metadata and sends it to Nurbak after the response is delivered.
export const config = {
matcher: ['/api/v1/:path*', '/api/v2/:path*']
}Every request to /api/* is captured via two mechanisms:
after() for serverless-safe flushing.http.Server.emit('request') to capture Pages Router API routes in Node.js runtime.Server Actions are detected via the Next-Action HTTP header and captured through a fetch interceptor. The SDK resolves action names from Next.js’s server-reference-manifest.json when available.
| Field | Description |
|---|---|
method | HTTP method (GET, POST, PUT, DELETE, etc.) |
path | Normalized route path (dynamic segments replaced with [id]) |
statusCode | HTTP status code |
statusCategory | 2xx, 3xx, 4xx, or 5xx |
durationMs | Request duration in milliseconds |
responseBytes | Response body size in bytes |
runtime | nodejs or edge |
region | Vercel region (when available via VERCEL_REGION) |
errorType | Error class name (on failures) |
errorMessage | Error message, truncated to 200 chars (on failures) |
actionHash | Server Action hash |
actionName | Resolved Server Action function name |
Dynamic segments are automatically replaced with [id] to group related routes:
/api/users/12345 → /api/users/[id]/api/orders/550e8400-e29b-41d4-a716-446655440000 → /api/orders/[id]/api/posts/abc123def456 → /api/posts/[id]Event lifecycle:
after() and waitUntil() ensure flushes complete before container freezeinitWatch(config)Initializes the SDK. Call this inside register() in your instrumentation.ts. Idempotent — calling it multiple times has no effect.
import { initWatch } from '@nurbak/watch'
initWatch({
apiKey: process.env.NURBAK_WATCH_KEY_LIVE!,
debug: true,
})withNurbakMiddleware(middleware?)Wraps your Next.js middleware to capture API route monitoring data. Returns a new middleware function.
import { withNurbakMiddleware } from '@nurbak/watch'
// Without existing middleware
export default withNurbakMiddleware()
// With existing middleware
export default withNurbakMiddleware(myMiddleware)flush()Manually flushes the event queue. Returns a Promise<void>.
import { flush } from '@nurbak/watch'
await flush()getSdkStatus()Returns the current SDK status for debugging.
import { getSdkStatus } from '@nurbak/watch'
const status = await getSdkStatus()
// { initialized: true, queueSize: 0, config: { ... } }interface NurbakWatchConfig {
apiKey: string
ingestUrl?: string
enabled?: boolean
debug?: boolean
sampleRate?: number
ignorePaths?: string[]
flushInterval?: number
maxBatchSize?: number
}
interface SdkStatus {
initialized: boolean
queueSize: number
config: NurbakWatchConfig | null
}
interface ApiCallEvent {
eventType: 'api_route' | 'server_action'
method: string
path: string
statusCode: number
statusCategory: '2xx' | '3xx' | '4xx' | '5xx'
responseBytes: number
startedAt: string
durationMs: number
runtime: 'nodejs' | 'edge'
region?: string
errorType?: string
errorMessage?: string
actionHash?: string
actionName?: string
}npx @nurbak/watch initInteractive setup wizard that detects your project and creates all necessary files.
npx @nurbak/watch initNon-interactive mode (for CI or AI assistants):
npx @nurbak/watch init --key nw_test_xxxxxxxxxxxxx| Flag | Description |
|---|---|
--key <key> | API key (nw_test_* or nw_live_*). Skips the interactive prompt. |
--help | Show CLI help. |
Nurbak Watch works on Vercel out of the box. The SDK uses after() from next/server and waitUntil() to ensure events are flushed before the serverless container freezes.
Set your environment variables in the Vercel dashboard:
NURBAK_WATCH_KEY_LIVE=nw_live_xxxxxxxxxxxxx
NURBAK_WATCH_KEY_TEST=nw_test_xxxxxxxxxxxxxThe SDK works identically on self-hosted Next.js. The http.Server interceptor captures all API route requests in long-running Node.js processes.
The middleware layer (withNurbakMiddleware) works in both Node.js and Edge runtimes. The instrumentation interceptor (initWatch) runs in the Node.js runtime only.
initWatch({ apiKey: '...', debug: true }) or set NURBAK_WATCH_DEBUG=truenw_test_ or nw_live_/api/:path*instrumentationHook: true is set in next.config.jsThe SDK automatically disables itself when NODE_ENV=test. To override:
initWatch({ apiKey: '...', enabled: true })If you see duplicate events, ensure initWatch() is only called once in instrumentation.ts. The SDK is idempotent — multiple calls are safe but unnecessary.
nw_test_* for development, nw_live_* for production).env.local