@browsonic/nextjs
v1.4.3
Published
Next.js adapter for @browsonic/sdk — App Router error pages, route-handler capture, instrumentation entry. Re-exports @browsonic/react. Apache-2.0.
Maintainers
Readme
@browsonic/nextjs
Next.js adapter for @browsonic/sdk — App Router error-page components (with optional pathname / params context), route-handler capture wrapper, Pages Router companions (browsonicPagesAppInit / browsonicPagesErrorInitialProps), Pages Router Atlas route templates (installPagesRouterAtlas / routeTemplateFromPathname), config wrapper, plus all the React-side primitives re-exported from @browsonic/react.
Status: current published surface — check npm or this package's
package.jsonfor the version. App RouterBrowsonicErrorPage/BrowsonicGlobalErrorPageaccept optionalpathname+paramsprops that consumers thread fromusePathname()/useParams()and land as thenextjs.pathnametag plus aparamssub-key on the consolidatednextjscontext bucket. Pages Router companions ship forpages/_app.tsx(browsonicPagesAppInit) andpages/_error.tsx(browsonicPagesErrorInitialProps), and Pages Router pages can feed App Atlas their route template viainstallPagesRouterAtlas(see below). Theinstrumentation.tshelper (@browsonic/nextjs/instrumentation) ships. Build-time source-map upload is handled by@browsonic/build-tools— since 1.4.0,withBrowsonicConfig({...}, { sourceMaps: { appKey } })registers its Webpack plugin automatically on production client builds (seenext.config.jsbelow); without thesourceMapsoption the wrapper stays an exact passthrough.
Why this adapter
Next.js's App Router has framework-specific error surfaces that the React adapter alone doesn't cover:
app/error.tsxis rendered by Next.js when a route subtree throws. The component is a Client Component that receives{ error, reset }. We ship a drop-in for it.app/global-error.tsxowns the<html>/<body>shell and is rendered when the root layout itself crashes. We ship a drop-in for it too.app/api/.../route.tsroute handlers run server-side. A wrapper forwards thrown errors to the SDK (when reachable) before re-throwing them so Next.js can serve its 500.
This package depends on @browsonic/react and re-exports its surface so Next.js consumers install one package, not two.
Install
npm install @browsonic/sdk @browsonic/react @browsonic/nextjs@browsonic/sdk, @browsonic/react, next (≥13.4), and react (18+) are all peer dependencies.
Quickstart — App Router error pages
// app/error.tsx
'use client';
import { BrowsonicErrorPage } from '@browsonic/nextjs';
export default BrowsonicErrorPage;// app/global-error.tsx
'use client';
import { BrowsonicGlobalErrorPage } from '@browsonic/nextjs';
export default BrowsonicGlobalErrorPage;The components capture { error, digest } to the SDK on mount, then render a minimal "Something went wrong" UI with a Try Again button. To customise, copy the 30-line implementation from src/error-page.tsx and adjust the JSX.
To attach route-context to captured errors, wrap the default export with pathname and params from Next's hooks:
// app/error.tsx
'use client';
import { usePathname, useParams } from 'next/navigation';
import { BrowsonicErrorPage } from '@browsonic/nextjs';
export default function ErrorPage(props: {
error: Error & { digest?: string };
reset: () => void;
}) {
return <BrowsonicErrorPage {...props} pathname={usePathname()} params={useParams()} />;
}The boundary tags the captured event with nextjs.pathname and lands params as a sub-key on the single consolidated nextjs context bucket (alongside runtime / source / pathname) so dashboards can group errors by route shape.
Quickstart — Pages Router (Next ≤ 12 / opt-in 13+)
For projects on the Pages Router, two companions cover the equivalent surfaces:
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { useEffect } from 'react';
import { browsonicPagesAppInit } from '@browsonic/nextjs';
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => browsonicPagesAppInit(), []);
return <Component {...pageProps} />;
}// pages/_error.tsx
import type { NextPage } from 'next';
import { browsonicPagesErrorInitialProps } from '@browsonic/nextjs';
interface ErrorProps {
statusCode: number;
pagePath?: string;
}
const Error: NextPage<ErrorProps> = ({ statusCode }) => <div>Error {statusCode}</div>;
// `browsonicPagesErrorInitialProps` IS the getInitialProps: it captures the
// error (browser-side only) and returns `{ statusCode, pagePath }` synchronously.
Error.getInitialProps = browsonicPagesErrorInitialProps;
export default Error;browsonicPagesAppInit registers window-level error / unhandledrejection listeners on the client (call it from useEffect; it returns a teardown so the listeners are removed on unmount / fast refresh) and is a no-op on the server. browsonicPagesErrorInitialProps captures whatever Next put on ctx.err, sets the consolidated nextjs context bucket (runtime: 'browser', source: 'pages-router-error', plus statusCode / pagePath / asPath when present), tags nextjs.pagePath, and records nextjsStatusCode metadata — capture only fires browser-side (it no-ops during SSR).
Quickstart — Pages Router Atlas route templates
Pages Router pages know their route template at runtime — router.pathname is the template, in Next's bracket dialect. Two helpers forward it to the SDK's page-view channel so App Atlas groups screens by parameterized route instead of raw URLs:
// pages/_app.tsx
import type { AppProps } from 'next/app';
import Router from 'next/router';
import { useEffect } from 'react';
import { installPagesRouterAtlas } from '@browsonic/nextjs';
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => installPagesRouterAtlas(Router), []);
return <Component {...pageProps} />;
}installPagesRouterAtlas(router) sends trackPageView(template) for the current page immediately (the initial view is exactly the page view that maps the entry screen), then again on every routeChangeComplete. It returns a teardown callback — return it from the useEffect so fast refresh never stacks listeners. Reporting failures never throw through a navigation. The router argument is a structural PagesRouterLike (pathname + events.on/off) — the default next/router singleton satisfies it, and the package takes no next/router dependency.
Pair it with the SDK init:
- SDK ≥ 3.14:
atlas: true— the core runs organic URL tracking from first paint and hands the page-view channel over on the first templatedtrackPageView, so there is no double counting and no extra flag. - Older cores:
manualPageViews: true— required, otherwise the organic history-driven page view double counts every navigation.
routeTemplateFromPathname(pathname) is the underlying converter, exported for hosts that drive trackPageView themselves: Next's bracket dialect becomes Atlas's colon dialect (/users/[id] → /users/:id; [...slug] / [[...slug]] catch-alls → *; other segments pass through verbatim). It returns '' for empty / non-string input so callers can fall back to the SDK's URL normalization.
App Router has no runtime route template, so these helpers are Pages Router only.
Quickstart — Route handlers
// app/api/checkout/route.ts
import { withBrowsonicRouteHandler } from '@browsonic/nextjs';
export const POST = withBrowsonicRouteHandler(async (req: Request) => {
const data = await req.json();
if (!data.email) throw new Error('email required');
return Response.json({ ok: true });
});The wrapper forwards the thrown Error to sdk.captureError, tags it with nextjsRouteHandler: 'true', and re-throws — Next.js's normal 500 path is preserved.
Quickstart — instrumentation.ts (Next 13.4+)
Next.js's project-root instrumentation.ts file convention runs once at server startup (register()) and on every unhandled server error (onRequestError). The @browsonic/nextjs/instrumentation sub-entry ships a one-line wire-up:
// instrumentation.ts (project root, alongside `next.config.mjs`)
import { browsonicInstrumentation } from '@browsonic/nextjs/instrumentation';
const { register, onRequestError } = browsonicInstrumentation({
apiEndpoint: process.env.BROWSONIC_API_ENDPOINT,
appKey: process.env.BROWSONIC_APP_KEY,
environment: process.env.VERCEL_ENV ?? process.env.NODE_ENV,
});
export { register, onRequestError };What ships today:
register()validates thatapiEndpoint+appKeyare present and emits oneconsole.warnper missing field. Misconfiguration surfaces at server boot instead of silently shipping pages with no telemetry.onRequestError(error, request, context)forwards the error toconsole.errorwith a structurednextjs.*context object (nextjs.path,nextjs.routerKind,nextjs.routePath,nextjs.routeType, etc.). Tests / custom log sinks override the report path via thereportErroroption.
What ships later (without a code change in your instrumentation.ts):
- Real server-runtime capture. The SDK is a browser library today, so
onRequestErrorforwards toconsole.errorrather than the ingest endpoint; a server-runtime transport is future work, independent of source maps (browser-side source-map upload already ships — see below). BROWSONIC_INSTRUMENTATION_VERSIONalready tags emitted events so future dashboards can distinguish wire-up generations.
Server-only sub-entry — the main @browsonic/nextjs bundle has no server code.
Quickstart — next.config.js
// next.config.mjs
import { withBrowsonicConfig } from '@browsonic/nextjs';
export default withBrowsonicConfig({
reactStrictMode: true,
// your config
});Without options, withBrowsonicConfig is an exact passthrough — adopt it now and turn on config-level integrations later without editing this file again.
Source-map upload (auto-registration, 1.4.0+)
Pass sourceMaps and the wrapper registers the @browsonic/build-tools Webpack plugin on production client builds automatically (dev rebuilds and server bundles are skipped):
// next.config.mjs
import { withBrowsonicConfig } from '@browsonic/nextjs';
export default withBrowsonicConfig(
{
reactStrictMode: true,
productionBrowserSourceMaps: true, // emit browser source maps at build time
},
{
sourceMaps: { appKey: 'web' }, // any @browsonic/build-tools option, e.g. release, debugIdInjection
},
);@browsonic/build-tools is an optional peer — install it alongside (npm install --save-dev @browsonic/build-tools). If it is missing, the wrapper logs a warning and your build continues untouched (source-map upload is a supporting flow and must never break next build). A user-supplied webpack hook keeps working: it runs first and the plugin is appended to the config it returns.
The sourceMaps object is forwarded verbatim to BrowsonicSourceMapsPlugin — appKey is required; release falls back to BROWSONIC_RELEASE → git short-sha → package.json version; the upload token comes from BROWSONIC_SOURCEMAP_TOKEN; debugIdInjection: true opts into per-build debugId stamping. The release the plugin uploads under must match the SDK's clientVersion. See the @browsonic/build-tools README for every option.
Prefer wiring the Webpack plugin yourself? The manual pattern keeps working:
// next.config.mjs
import { BrowsonicSourceMapsPlugin } from '@browsonic/build-tools/webpack';
const nextConfig = {
productionBrowserSourceMaps: true,
webpack(config, { isServer }) {
if (!isServer) {
config.plugins.push(new BrowsonicSourceMapsPlugin({ appKey: 'web' }));
}
return config;
},
};
export default nextConfig;Quickstart — Boundary inside Client Components
The full React surface re-exports through this package, so anywhere in your 'use client' tree you can:
'use client';
import { BrowsonicErrorBoundary, useUser } from '@browsonic/nextjs';
export function App() {
useUser({ id: 'u1' });
return (
<BrowsonicErrorBoundary fallback={(error, reset) => <div>{error.message}</div>}>
<Routes />
</BrowsonicErrorBoundary>
);
}Naming note
withBrowsonic is the React HOC (re-exported from @browsonic/react).
withBrowsonicConfig is the Next.js config wrapper (this package).
The split mirrors @sentry/nextjs's withSentryConfig pattern.
Defensive contract
Same as every other adapter:
- The host app must never crash because reporting failed.
- SDK calls are wrapped in
try { ... } catch {}. - All surfaces work without an SDK present (page still renders, route handler still throws upstream, config still passes through).
What this package does NOT do (yet)
- Auto-detected
instrumentation.tsinjection. The shipped helper is opt-in (consumer pastes a 5-line wire-up). A build-time injector that creates the file automatically would need a transform on every project's root, which is more invasive than the consumer-opt-in convention. - Server-runtime capture. The SDK is a browser library; route-handler errors that occur in pure Node have no
windowto write to. The wrapper still re-throws so your handler returns its expected status. - Edge runtime instrumentation. Edge runtimes lack a stable global Browsonic singleton — adopt the SDK in the client layer and use the route-handler wrapper for opportunistic capture.
- Pages Router data layer instrumentation (
getServerSideProps/getStaticProps). Will be revisited only if Pages Router consumer demand surfaces.
License
Apache-2.0. See the repo root LICENSE and the package NOTICE.
