npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mwjb/next-rum-monitor

v1.0.5

Published

Comprehensive RUM monitoring for Next.js (SSR, Client, Axios, Build-time)

Downloads

218

Readme

Next RUM Monitor

A comprehensive Real User Monitoring (RUM) solution for Next.js applications.

Features

  • Client-side tracking: JS errors, unhandled rejections, fetch/XHR failures, and page load performance.
  • Server-side tracking: SSR errors, metadata generation errors, and instrumentation hooks.
  • Axios integration: Automatically report HTTP 4xx/5xx errors and slow requests (>500ms by default).
  • Build-time tracking: Capture Webpack compilation errors during dev and build.
  • API key authentication: Optional apiKey sent as X-API-Key on every ingest request (e.g. Laravel applications.api_key).
  • Detailed context: Device info, network quality, and arbitrary extra metadata you define.

Installation

npm install @mwjb/next-rum-monitor
# or
pnpm add @mwjb/next-rum-monitor
# or
yarn add @mwjb/next-rum-monitor

Setup

1. Environment variables

Create a .env.local file in your Next.js project and add:

# RUM ingest URL (e.g. Laravel POST /api/rum-events)
NEXT_PUBLIC_RUM_API=https://rum.mwjb.net/api/rum-events

# Application API key (sent as X-API-Key header)
NEXT_PUBLIC_RUM_API_KEY=your-application-api-key

| Variable | Required | Description | | ------------------------- | -------- | ----------------------------------------------------------------- | | NEXT_PUBLIC_RUM_API | No | RUM endpoint URL. Used on client and server. | | NEXT_PUBLIC_RUM_API_KEY | Yes* | API key for browser ingest (X-API-Key). | | RUM_API_KEY | No | Server-only fallback for SSR instrumentation and build reporting. |

Required when your backend enforces API key auth (e.g. Laravel RumEventController).

Note: Browser events are sent from the client, so the key must be available via NEXT_PUBLIC_* and will be included in the client bundle. Treat it as a public ingest key (similar to analytics keys), not a secret server credential.

After adding or changing these variables, restart your Next.js dev server.

2. Central configuration (recommended)

Define shared settings once and reuse them across all integration points:

// src/lib/rum/config.ts
import type {RumConfig} from "@mwjb/next-rum-monitor";

const DEFAULT_RUM_ENDPOINT = "https://rum.mwjb.net/api/rum-events";

export const rumEndpoint =
  process.env.NEXT_PUBLIC_RUM_API?.trim() || DEFAULT_RUM_ENDPOINT;

export const rumApiKey =
  process.env.NEXT_PUBLIC_RUM_API_KEY?.trim() ||
  process.env.RUM_API_KEY?.trim() ||
  undefined;

export const rumConfig: RumConfig = {
  endpoint: rumEndpoint,
  apiKey: rumApiKey,
  lowSpeedThreshold: 500,
  includeResponse: true,
  includeMemory: true,
  includeDeviceInfo: true,
  includeNetworkInfo: true,
  extra: {
    // Any JSON-serializable key-value data your backend understands
    ...JSON.parse(process.env.NEXT_PUBLIC_RUM_EXTRA ?? "{}"),
  },
};

export const rumBoundaryReportOptions = {
  endpoint: rumEndpoint,
  apiKey: rumApiKey,
};
// src/lib/rum/index.ts
export {
  rumApiKey,
  rumConfig,
  rumEndpoint,
  rumBoundaryReportOptions,
} from "./config";

3. Browser initialization

Add this to your root layout.tsx:

import {initRum} from "@mwjb/next-rum-monitor";
import {rumConfig} from "@/lib/rum";

initRum(rumConfig);

4. Next.js configuration

Wrap your config in next.config.ts:

import {withRumBuildReporter} from "@mwjb/next-rum-monitor";
import {rumConfig} from "./src/lib/rum/config";

export default withRumBuildReporter(nextConfig, rumConfig);

5. Server instrumentation

Create or update src/instrumentation.ts:

import {createInstrumentationHook} from "@mwjb/next-rum-monitor";
import {rumConfig} from "@/lib/rum";

export const onRequestError = createInstrumentationHook(rumConfig);

6. Axios interceptors

Apply to your axios instance:

import {applyAxiosRumInterceptors} from "@mwjb/next-rum-monitor";
import {rumConfig} from "@/lib/rum";

applyAxiosRumInterceptors(axiosInstance, rumConfig, () => ({
  key_1: value_1,
  key_2: value_2,
  // Any fields your ingest pipeline expects
}));

For per-request dynamic metadata, use the callback context:

applyAxiosRumInterceptors(axiosInstance, rumConfig, ({requestConfig, type, status}) => ({
  type,
  status,
  route: window.location.pathname,
  requestId: (requestConfig as any)?.headers?.["x-request-id"],
}));

7. Error boundaries (recommended)

React errors caught by error.tsx / global-error.tsx do not always reach window.onerror. Report them explicitly with your own UI (i18n, design system, etc.):

// src/app/error.tsx
"use client";

import {useRumBoundaryError} from "@mwjb/next-rum-monitor/components";
import {rumBoundaryReportOptions} from "@/lib/rum";

export default function Error({
  error,
  reset,
}: {
  error: Error & {digest?: string};
  reset: () => void;
}) {
  useRumBoundaryError(error, "segment", rumBoundaryReportOptions);

  return (
    <div>
      {/* Your localized / styled fallback */}
      <button type="button" onClick={() => reset()}>
        Try again
      </button>
    </div>
  );
}
// src/app/global-error.tsx
"use client";

import {useRumBoundaryError} from "@mwjb/next-rum-monitor/components";
import {rumBoundaryReportOptions} from "@/lib/rum";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & {digest?: string};
  reset: () => void;
}) {
  useRumBoundaryError(error, "global", rumBoundaryReportOptions);

  return (
    <html>
      <body>
        <button type="button" onClick={() => reset()}>
          Try again
        </button>
      </body>
    </html>
  );
}

Without React, call reportRumBoundaryError from the main package inside useEffect:

import {reportRumBoundaryError} from "@mwjb/next-rum-monitor";
import {rumBoundaryReportOptions} from "@/lib/rum";

useEffect(() => {
  reportRumBoundaryError(error, "segment", rumBoundaryReportOptions);
}, [error]);

Optional: @mwjb/next-rum-monitor/components

RumError and RumGlobalError are report-only helpers (no built-in UI). They exist for quick wiring when you pass children:

import {RumError} from "@mwjb/next-rum-monitor/components";
import {rumBoundaryReportOptions} from "@/lib/rum";

export default function Error(props: {error: Error; reset: () => void}) {
  return (
    <RumError {...props} {...rumBoundaryReportOptions}>
      <YourErrorUI error={props.error} onRetry={props.reset} />
    </RumError>
  );
}

Prefer useRumBoundaryError or reportRumBoundaryError for production apps.

Configuration reference

RumConfig

| Option | Type | Default | Description | | -------------------- | --------- | ------- | ------------------------------------------- | | endpoint | string | — | RUM ingest URL (required). | | apiKey | string | — | Sent as X-API-Key on all ingest requests. | | lowSpeedThreshold | number | 500 | Report requests slower than this (ms). | | includeResponse | boolean | true | Include HTTP response body in events when allowed by the rules below. | | includeResponseOnErrorOnly | boolean | false | When true, attach extra.response only for HTTP status >= 400 (axios low-speed, axios http-error, fetch http-error). | | includeMemory | boolean | true | Include memory usage in events. | | includeDeviceInfo | boolean | true | Include screen and viewport size. | | includeNetworkInfo | boolean | true | Include navigator.connection quality. | | extra | RumExtraData \| (() => RumExtraData) | — | Arbitrary metadata merged into every event's extra object. |

Per-event overrides: sendRumEvent({ type: "custom", extra: { requestId: "abc" } }). Config keys are merged first; later keys win.

extra: {
  // Any shape — strings, numbers, nested objects, arrays, etc.
  tenant: process.env.NEXT_PUBLIC_TENANT,
  tags: ["web", "nextjs"],
},

extra: () => ({
  locale: document.documentElement.lang || "en",
  path: window.location.pathname,
}),

Event payload shape

Every ingest POST uses a minimal, system-agnostic envelope:

| Field | Description | | --- | --- | | type | Event category (js-error, http-error, page-load, …) | | timestamp | Unix ms | | url | Page URL (client) or request/build URL (server) | | memory | Optional memory snapshot when enabled | | extra | All other context — source, message, stack, HTTP details, your config metadata, etc. |

Example:

{
  "type": "http-error",
  "timestamp": 1710000000000,
  "url": "https://app.example/en/dashboard",
  "memory": { "usedJSHeapSize": "42MB" },
  "extra": {
    "source": "axios",
    "method": "GET",
    "requestUrl": "/api/users",
    "status": 500,
    "userId": "123"
  }
}

RumExtraData

Record<string, unknown> — attach any JSON-serializable metadata your ingest backend accepts.

Error boundary helpers

| Export | Package | Description | | ----------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | reportRumBoundaryError(error, kind, options?) | main | Report segment ("segment") or global ("global") boundary errors. Uses sendRumEvent when initRum() ran; otherwise options.endpoint / options.apiKey for a direct ingest POST. | | useRumBoundaryError(error, kind, options?) | ./components | React hook wrapper around reportRumBoundaryError. | | RumError / RumGlobalError | ./components | Optional report-only wrappers; pass your UI as children. |

Laravel backend

When using a Laravel ingest endpoint that validates X-API-Key against applications.api_key:

$apiKey = $request->header('X-API-Key');
$application = Application::where('api_key', $apiKey)
    ->where('is_active', true)
    ->first();

Set apiKey in rumConfig to the same value as the application's api_key column.

Integration checklist

| File | Why? | | -------------------------- | ------------------------------------------------------------- | | src/lib/rum/config.ts | Single source of truth for endpoint, apiKey, and options. | | next.config.ts | Wrap with withRumBuildReporter to catch compilation errors. | | src/instrumentation.ts | Capture SSR and Server Action errors via onRequestError. | | src/app/layout.tsx | Call initRum() to bootstrap browser monitoring. | | src/app/error.tsx | Catch React rendering errors within pages and segments. | | src/app/global-error.tsx | Safety net for errors in the root layout. | | src/lib/axios/axios.tsx | Track HTTP 4xx/5xx and latency (if using Axios). |

Docker & CI

NEXT_PUBLIC_RUM_* values are embedded in the client bundle at build time. Treat NEXT_PUBLIC_RUM_API_KEY as a public ingest key (like an analytics key), not a server secret. Use RUM_API_KEY (no NEXT_PUBLIC_ prefix) for server-only reporting when you need a value that never ships to the browser.

Do not commit keys in Dockerfiles, shell scripts, or workflow YAML. Store them in GitHub Actions secrets (or your CI provider's equivalent) and inject them at build/runtime.

1. Add repository secrets

In GitHub: Settings → Secrets and variables → Actions → New repository secret

| Secret | Used for | | --- | --- | | NEXT_PUBLIC_RUM_API | RUM ingest URL (client + server) | | NEXT_PUBLIC_RUM_API_KEY | Browser ingest key (X-API-Key) | | RUM_API_KEY | Optional server-only fallback (SSR / build reporting) |

2. Dockerfile (build args only — values come from CI)

ARG NEXT_PUBLIC_RUM_API
ARG NEXT_PUBLIC_RUM_API_KEY
ARG RUM_API_KEY

ENV NEXT_PUBLIC_RUM_API=$NEXT_PUBLIC_RUM_API
ENV NEXT_PUBLIC_RUM_API_KEY=$NEXT_PUBLIC_RUM_API_KEY
ENV RUM_API_KEY=$RUM_API_KEY

Do not hardcode real URLs or keys in the Dockerfile.

3. GitHub Actions workflow

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        env:
          NEXT_PUBLIC_RUM_API: ${{ secrets.NEXT_PUBLIC_RUM_API }}
          NEXT_PUBLIC_RUM_API_KEY: ${{ secrets.NEXT_PUBLIC_RUM_API_KEY }}
          RUM_API_KEY: ${{ secrets.RUM_API_KEY }}
        run: |
          docker build \
            --build-arg NEXT_PUBLIC_RUM_API="$NEXT_PUBLIC_RUM_API" \
            --build-arg NEXT_PUBLIC_RUM_API_KEY="$NEXT_PUBLIC_RUM_API_KEY" \
            --build-arg RUM_API_KEY="$RUM_API_KEY" \
            -t your-app:${{ github.sha }} .

Secrets are masked in workflow logs and never committed to the repo.

Local builds

For local Docker builds, pass values from your shell environment (e.g. .env.local loaded in the terminal — not copied into the image layer history in committed files):

export $(grep -v '^#' .env.local | xargs)

docker build \
  --build-arg NEXT_PUBLIC_RUM_API="$NEXT_PUBLIC_RUM_API" \
  --build-arg NEXT_PUBLIC_RUM_API_KEY="$NEXT_PUBLIC_RUM_API_KEY" \
  --build-arg RUM_API_KEY="$RUM_API_KEY" \
  -t your-app .