@sophonz/nextjs
v0.0.7
Published
One-config Sophonz monitoring for Next.js — browser and server, on one trace
Readme
@sophonz/nextjs
Sophonz monitoring for Next.js. Configures @sophonz/browser-sdk and
@sophonz/node-sdk from one config, so browser and server spans share a trace.
Part of the Sophonz OpenTelemetry suite.
Install
bun add @sophonz/nextjs
# or
pnpm add @sophonz/nextjs
# or
npm install @sophonz/nextjsSetup
instrumentation.ts at the project root:
export { register } from '@sophonz/nextjs/server';app/layout.tsx:
import { SophonzProvider } from '@sophonz/nextjs/client';
export default function RootLayout({ children }) {
return (
<html>
<body>
<SophonzProvider>{children}</SophonzProvider>
</body>
</html>
);
}.env:
NEXT_PUBLIC_SOPHONZ_COLLECTOR_URL=https://in.sophonz.ai
NEXT_PUBLIC_SOPHONZ_APP_NAME=my-app
NEXT_PUBLIC_SOPHONZ_APP_VERSION=1.0.0
NEXT_PUBLIC_SOPHONZ_APP_KEY=your-app-key
NEXT_PUBLIC_SOPHONZ_PROJECT=my-project
NEXT_PUBLIC_SOPHONZ_ENVIRONMENT=productionNEXT_PUBLIC_ is required — Next inlines only that prefix into the client
bundle. appKey therefore reaches the browser; it is an ingestion key, not an
access token.
next.config.js:
module.exports = {
serverExternalPackages: ['@sophonz/nextjs', '@sophonz/node-sdk'],
};OpenTelemetry patches modules through Node's require hook. Bundled modules are
not required at runtime, so the hook misses them and database and HTTP client
spans are lost.
Configuring in code
Config passed in wins over the environment.
// instrumentation.ts
import { register as sophonz } from '@sophonz/nextjs/server';
export const register = () =>
sophonz({
appName: 'my-app',
server: { advancedNetworkCapture: true },
});<SophonzProvider
appName="my-app"
tracePropagationTargets={[/^https:\/\/api\.example\.com/]}
browser={{ disableReplay: false }}
/>Config
| Option | Type | Description |
|--------|------|-------------|
| collectorUrl | string | OTLP collector base URL |
| appName | string | service.name |
| appVersion | string | service.version |
| appKey | string | service.key. Reaches the browser |
| project | string | service.namespace |
| deploymentEnvironment | string | e.g. production |
| tracePropagationTargets | (string | RegExp)[] | Origins the browser sends traceparent to. Default: current origin |
| debug | boolean | Verbose logging on both sides |
| browser | object | Passed to @sophonz/browser-sdk |
| server | object | Passed to @sophonz/node-sdk |
Use browser and server to reach any option of the underlying SDKs.
Defaults this package sets:
tracePropagationTargets: current origin only. Cross-origin requires the other end to allowtraceparentin CORS.- Server
betaMode: true— required forsetTraceAttributes()to work. - Server
disableStartupLogs: true— suppresses the SDK's startup spinner. - Browser
disableIntercom: true— otherwise the SDK polls for a globalIntercomand logs an error when absent.
Collected
Next.js (verified on Next 16): BaseServer.handleRequest,
AppRender.getBodyResult, AppRender.fetch, NextNodeServer.startResponse,
with next.route, next.rsc, next.segment, next.span_type.
Server SDK: HTTP server and client spans, database and Redis clients, Node
runtime metrics, console.* as logs, and an error log record for every span
ending with ERROR status, carrying that span's trace.id.
Browser SDK: page loads, Web Vitals, user interactions, fetch and XHR, JavaScript errors, optional session replay.
Request attributes
Next has no middleware chain, so @sophonz/node-sdk's Express/Koa/Fastify
helpers do not apply.
import { setTraceAttributes } from '@sophonz/node-sdk';
export async function GET() {
setTraceAttributes({ 'user.id': userId });
}Deployment modes
| Mode | Server monitoring | Notes |
|------|-------------------|-------|
| next start | yes | |
| output: 'standalone' | yes | Run node .next/standalone/server.js |
| Serverless (Vercel, Lambda) | partial | Spans can be lost between invocations |
| output: 'export' | n/a | No server; provider still works |
| Edge runtime / middleware | no | |
Serverless: the platform freezes the process between invocations, so the batch
processor's timer may not fire. @sophonz/node-sdk exports shutdown() but no
forceFlush(), so there is no per-invocation flush. Long-lived instances are
unaffected.
Edge runtime: register() returns without starting when
NEXT_RUNTIME === 'edge'. The check is written so the bundler folds it, keeping
the Node SDK out of the Edge bundle. Middleware and runtime = 'edge' routes
are not traced; requests through them are traced once they reach a Node route.
Verifying
Browser console:
window.Sophonz.inited // true
document.cookie // contains __sophonz_rum_sidTrace propagation:
curl -H "traceparent: 00-abababababababababababababababab-1111111111111111-01" \
http://localhost:3000/api/your-routeServer spans for that request carry abababababababababababababababab.
Local collector for testing:
node -e "require('http').createServer((q,s)=>{let b=[];q.on('data',c=>b.push(c));q.on('end',()=>{console.log(q.url,Buffer.concat(b).length+'B');s.writeHead(200,{'access-control-allow-origin':'*','access-control-allow-methods':'POST, OPTIONS','access-control-allow-headers':'*'});s.end('{}')})}).listen(4318)"Set NEXT_PUBLIC_SOPHONZ_COLLECTOR_URL=http://localhost:4318. The CORS headers
are required — without them the browser's export fails preflight.
Troubleshooting
No console output, nothing collected. The provider did not mount. Verify
@sophonz/nextjs is built. In a monorepo, run the app through a task that
builds dependencies first; next dev alone rebuilds nothing.
Browser data but no server data. The halves point at different collectors.
register() derives server endpoints from collectorUrl, but an explicitly set
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT takes precedence.
Server spans have their own trace id. The browser is not propagating. Add
the API origin to tracePropagationTargets, and allow traceparent in its CORS
config.
Module not found: Can't resolve <dynamic>. Fixed in @sophonz/node-sdk
0.2.5. Earlier versions resolve module names at runtime, which bundlers report
on every compile. Collection is unaffected.
Config changes have no effect. NEXT_PUBLIC_ values are inlined at build
time. Restart the dev server; Fast Refresh does not re-inline them.
API
@sophonz/nextjs/server
register(config?)— starts the server SDK. Returns{ started, reason?, missing? }.reasonis'edge-runtime','already-started'or'incomplete-config'.
@sophonz/nextjs/client
SophonzProvider— Client Component. Initializes once on mount, imports the browser SDK dynamically.
@sophonz/nextjs
resolveConfig(input?)— merges explicit config over the environment.missingFields(config)— required fields that are absent.ENV_KEYS— env var names.SophonzNextConfig— config type.
Peer Dependencies
next>= 13.4.0react>= 18
License
See LICENSE in this package.
Part of sophonz-js.
