Skip to main content

Next.js

Add Monitoring to a Next.js app with the @accessible-so/monitor-next adapter, in the App Router or the Pages Router.

On this page

Next.js has its own adapter, @accessible-so/monitor-next. It renders the loader for you and adds the one thing a bare <script> tag cannot work out on its own: which route pattern a concrete URL belongs to, so findings group by /products/[slug] rather than by one row per product.

Install the adapter

Terminal
pnpm add @accessible-so/monitor-next

Peer dependencies are next >=13.4 <17 and react >=18. Component attribution additionally requires Next 16; everything else on this page works across the whole range.

Add the component

Render it once, at the root. Everything below it on the page is covered — there is nothing to add per page or per route.

app/layout.tsx
// app/layout.tsx
import { AccessibleMonitor } from '@accessible-so/monitor-next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <AccessibleMonitor siteKey="acc_rum_YOUR_SITE_KEY" />
      </body>
    </html>
  );
}

Replace acc_rum_YOUR_SITE_KEY with your site's own key — copy the snippet from your domain's Monitoring page, which already carries it.

Either component registers route-pattern attribution and renders the loader <Script>, so you don't need next/script as well. Neither one starts the engine: sampling and when the engine downloads stay the loader's own decision, exactly as they are without Next in the picture. See Configuration for the props both components take.

What the pattern looks like

The pattern reported is the app route — what your own file tree calls it. If your app runs under basePath: '/shop' or serves /fr/... locale prefixes, the pattern is still /products/[slug], never /shop/products/[slug], so the same page groups as one route across every prefix it is served under.

Component attribution

Optional, and off unless you wrap your config. It stamps the outermost element a named function component returns, so a finding can be attributed back to the component that rendered it:

next.config.mjs
// next.config.mjs
import { withAccessible } from '@accessible-so/monitor-next/config';

export default withAccessible({
  // your existing config
});

Pass withAccessible(nextConfig, { componentAttribution: false }) to skip the plugin. Everything else keeps working and a finding's component is simply left unattributed.

Attribution needs Next 16, and says so rather than failing your build

The stamping plugin is a WebAssembly binary that exchanges a syntax tree with Next's compiler, in a layout fixed by the compiler version it was built against. Ours is built for Next 16. On any other major — or when the binary is absent from the installed package, which happens when it was packed on a machine with no Rust toolchain — withAccessible prints one line and hands your config back untouched rather than failing next build:

[accessible] component attribution needs Next 16 (found 15.5.9); building without it

Everything else keeps working. Findings then carry no component, never a guessed one.

What gets stamped

Only the top-level return of a PascalCase named function component. That leaves several ordinary shapes unattributed, by design:

  • An early return (if (!data) return <Empty/>;), a ternary, or a wrapped arrow (const Card = memo(() => <div/>)).
  • A component that returns another component (function Page() { return <Layout/> }) or a fragment (function List() { return <><p/></> }) — the stamp belongs to whichever component's file actually renders the host element.
  • An anonymous default export (export default function () {}), and anything named in camelCase, which is indistinguishable from a helper.
  • An element that already carries data-a11y-component or data-a11y-source, and anything under node_modules.

The stamped line is the component's declaration, not the element's, so it stays put when the body is reformatted.

Compatibility

The App Router path is tested end to end in a real browser, against both next build --webpack and next build --turbopack, on Next 16.3.5.

The peer range is wider than what that suite covers. Next 13.4 through 15 have not been run in a browser, and neither has the Pages Router — AccessibleMonitorPages has unit tests only. On those versions the components and route patterns work as described; only component attribution is unavailable, and it declines rather than breaking the build.

Known limits

All of these end the same way: the finding is still reported, and the route pattern on it is absent rather than wrong. A mislabelled pattern costs more to chase than a missing one.

  • An ambiguous URL is reported by its raw path. Rebuilding /products/[slug] means matching each param's value back to the segment it came from, and some URLs have more than one answer — two params sharing a value, a value that appears in more than one segment, a catch-all run that matches more than one position. Those are left as the concrete path.
  • The route map holds the last 50 paths. A finding reported against a page you left more than 50 navigations ago carries no pattern.
  • An unusual prefix costs the first moment after a navigation. The adapter predicts the browser path a route will appear at from a prefix it measures once, on first render; under a basePath, trailingSlash or locale arrangement it cannot relate to the router's own path, that prediction is skipped. An effect writes the real path immediately after the commit, so only a finding recorded in that window is affected.
  • AccessibleMonitorPages in an App Router app reports no patterns at all. It reads next/router, which does not exist there. The loader still loads and findings still arrive — they simply carry no route.

Check it's working

See Debugging to confirm the script is reporting.