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

@cherrypeak-org/cherryboard-web

v1.3.0

Published

Browser & React error tracking client for the CherryBoard dashboard (CherryPeak).

Readme

@cherrypeak-org/cherryboard-web

Browser & React error tracking for the CherryBoard dashboard. Drop it into any React app (Next.js, Vite, CRA) and uncaught errors, promise rejections, and React render errors are captured, batched, and shipped to your CherryBoard project — where they show up as grouped issues alongside your backend errors.

  • Zero-config capture — global errors, unhandled rejections, resource failures.
  • React-nativeErrorBoundary, a provider, hooks, and Next.js App Router helpers.
  • Reliable — batching, offline queue, retry, dedupe, rate-limiting, keepalive.
  • Private & safe — PII scrubbing before send, SSR-safe, tiny, zero-dependency core.
  • Readable stacks — upload source maps and see original files, lines and functions.

Wire-compatible with the same /api/v1/errors ingest the .NET CherryPeak.CherryBoard.Client uses — one dashboard, both stacks.


Contents

  1. Install
  2. Get an ingest API key
  3. Quick start — Next.js (App Router)
  4. Quick start — Vite / CRA / plain React
  5. Reporting errors manually
  6. Identifying users & adding context
  7. Configuration reference
  8. What gets captured
  9. Security & privacy
  10. Troubleshooting
  11. Readable stack traces (source maps)
  12. Server-side errors (Next.js)
  13. Why didn't my error show up?
  14. Performance & visits

1. Install

npm install @cherrypeak-org/cherryboard-web
# or: pnpm add / yarn add

react and react-dom are optional peer dependencies — you only need them if you import from @cherrypeak-org/cherryboard-web/react. The core works in any browser app.


2. Get an ingest API key

  1. Open the CherryBoard dashboard → your Project → the Environment you want errors filed under (e.g. Production).
  2. Create an API key and copy it. This key resolves the project + environment server-side, so front-end errors land in the right place automatically.

⚠️ This key ships in your browser bundle and is public. That's expected (it's how every browser error tracker works), but see Security for how to keep it safe (write-only scope, CORS, rate limiting).

Put the key and API host in public env vars:

# .env.local  (Next.js)   — NEXT_PUBLIC_ vars are exposed to the browser
NEXT_PUBLIC_CHERRYBOARD_KEY=cpd_xxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_CHERRYBOARD_URL=https://<your-cherryboard-api-host>
# .env  (Vite)
VITE_CHERRYBOARD_KEY=cpd_xxxxxxxxxxxxxxxxxxxxxxxx
VITE_CHERRYBOARD_URL=https://<your-cherryboard-api-host>

apiUrl is the API host root — the SDK appends /api/v1/errors/batch for you.


3. Quick start — Next.js (App Router)

Works with Next.js 13.4+ (App Router) including Next.js 16 / React 19.

3a. Initialize once, as early as possible

Create instrumentation-client.ts at your project root (or src/). Next.js runs this on the client before your app hydrates — the ideal place to start the tracker.

// instrumentation-client.ts
import { init } from '@cherrypeak-org/cherryboard-web';

init({
  apiKey: process.env.NEXT_PUBLIC_CHERRYBOARD_KEY!,
  apiUrl: process.env.NEXT_PUBLIC_CHERRYBOARD_URL!,
  environment: process.env.NEXT_PUBLIC_ENV ?? 'production',
  release: process.env.NEXT_PUBLIC_COMMIT_SHA, // optional, recommended
});

On older Next without instrumentation-client.ts, use the <CherryBoardProvider> instead.

3b. Report render errors from the App Router boundaries

Next catches render errors with error.tsx (per route) and global-error.tsx (root layout). They only render a fallback — add one line to also report them:

// app/error.tsx
'use client';
import { useEffect } from 'react';
import { captureRouteError } from '@cherrypeak-org/cherryboard-web/react';

export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
  useEffect(() => {
    captureRouteError(error); // includes the Next.js `digest` for correlation
  }, [error]);

  return (
    <div>
      <h2>Something went wrong.</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}
// app/global-error.tsx
'use client';
import { useEffect } from 'react';
import { captureRouteError } from '@cherrypeak-org/cherryboard-web/react';

export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
  useEffect(() => {
    captureRouteError(error);
  }, [error]);

  return (
    <html>
      <body>
        <h2>Application error.</h2>
        <button onClick={reset}>Reload</button>
      </body>
    </html>
  );
}

That's it — global errors, unhandled rejections, and route-level render errors are now tracked.

3c. (Optional) Wrap a subtree in an ErrorBoundary

For a nicer fallback around a specific area (and to keep the rest of the page alive):

'use client';
import { ErrorBoundary } from '@cherrypeak-org/cherryboard-web/react';

export function Widget() {
  return (
    <ErrorBoundary fallback={({ reset }) => <button onClick={reset}>Reload widget</button>}>
      <FlakyChart />
    </ErrorBoundary>
  );
}

3d. Alternative: the provider

If you'd rather not use instrumentation-client.ts, initialize with the provider in your root layout. It's a client component, so it's safe to render from the (server) layout:

// app/layout.tsx
import { CherryBoardProvider } from '@cherrypeak-org/cherryboard-web/react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <CherryBoardProvider
          config={{
            apiKey: process.env.NEXT_PUBLIC_CHERRYBOARD_KEY!,
            apiUrl: process.env.NEXT_PUBLIC_CHERRYBOARD_URL!,
            environment: process.env.NEXT_PUBLIC_ENV ?? 'production',
          }}
        >
          {children}
        </CherryBoardProvider>
      </body>
    </html>
  );
}

Amplify note: on AWS Amplify, only env vars matching the build's allowlist reach the runtime. NEXT_PUBLIC_* vars are picked up automatically — make sure your key uses that prefix.


4. Quick start — Vite / CRA / plain React

Call init once at your entry point, before rendering:

// main.tsx
import { init } from '@cherrypeak-org/cherryboard-web';

init({
  apiKey: import.meta.env.VITE_CHERRYBOARD_KEY,
  apiUrl: import.meta.env.VITE_CHERRYBOARD_URL,
  environment: import.meta.env.MODE,
});

// ...then ReactDOM.createRoot(...).render(<App />)

Wrap your tree in the boundary to catch render errors:

import { ErrorBoundary } from '@cherrypeak-org/cherryboard-web/react';

<ErrorBoundary fallback={<p>Something went wrong.</p>}>
  <App />
</ErrorBoundary>

5. Reporting errors manually

Error boundaries and global handlers can't see errors you catch yourself (in try/catch, event handlers, or async code). Report those explicitly:

import { captureException, captureMessage } from '@cherrypeak-org/cherryboard-web';

try {
  await riskyThing();
} catch (err) {
  captureException(err, { context: { orderId, step: 'checkout' } });
}

// Log a noteworthy non-error event
captureMessage('Payment provider returned an unexpected shape', 'Warning');

In components, the hook gives you a stable callback:

'use client';
import { useCaptureError } from '@cherrypeak-org/cherryboard-web/react';

function SaveButton() {
  const capture = useCaptureError();
  const onClick = async () => {
    try {
      await save();
    } catch (err) {
      capture(err, { context: { feature: 'save' } });
    }
  };
  return <button onClick={onClick}>Save</button>;
}

6. Identifying users & adding context

import { setUser, setTag, addBreadcrumb } from '@cherrypeak-org/cherryboard-web';

// After login — keep it to an opaque id; do NOT pass emails/names.
setUser({ id: user.id });

// Global tags attached to every subsequent event
setTag('tenant', tenantId);
setTag('plan', 'pro');

// A manual breadcrumb
addBreadcrumb({ category: 'ui', message: 'Opened export dialog' });

// On logout
setUser(null);

Navigation, clicks, and fetch calls are recorded as breadcrumbs automatically.


7. Configuration reference

Only apiKey and apiUrl are required.

| Option | Type | Default | Description | |---|---|---|---| | apiKey | string | — | Ingest API key, sent as X-API-Key. | | apiUrl | string | — | API host root; /api/v1/errors/batch is appended. | | environment | string | "production" | Tag shown in the dashboard metadata. | | release | string | – | App version / git SHA (regression tracking, source maps). | | enabled | boolean | true | Master switch — set false to disable entirely. | | sampleRate | number | 1 | Fraction of events kept (0–1). | | maxBatchSize | number | 20 | Events per request (backend caps at 100). | | flushIntervalMs | number | 4000 | Debounce before an idle buffer flushes. | | maxQueueItems | number | 100 | Max events persisted to the offline queue. | | maxRetries | number | 3 | Retry attempts (5xx / network only). | | maxBreadcrumbs | number | 30 | Breadcrumbs retained per event. | | offlineStorage | boolean | true | Persist undelivered events to localStorage. | | captureUnhandledErrors | boolean | true | Capture window uncaught errors. | | captureUnhandledRejections | boolean | true | Capture unhandled promise rejections. | | captureResourceErrors | boolean | true | Capture failed img/script/css loads. | | captureConsole | boolean | true | Turn console.error/warn into breadcrumbs. | | autoBreadcrumbs | boolean | true | Auto navigation/click/fetch breadcrumbs. | | denyUrls | (string \| RegExp)[] | [] | Drop events whose stack/URL matches. | | allowUrls | (string \| RegExp)[] | [] | If set, keep only matching events. | | beforeSend | (event) => event \| null | – | Mutate/scrub, or drop (return null). | | trackPerformance | boolean | false | Collect API request timings and page views. See §14. | | metricsFlushIntervalMs | number | 60000 | How often performance rollups are posted. | | debug | boolean | false | Log SDK diagnostics to the console. |

beforeSend

Runs after the built-in PII scrub, right before an event is queued:

init({
  apiKey, apiUrl,
  beforeSend(event) {
    if (event.message.includes('ResizeObserver loop')) return null; // drop noise
    event.context.build = '2026.7.1';
    return event;
  },
});

8. What gets captured

| Source | How | Severity | |---|---|---| | Uncaught exceptions | window error | Error | | Unhandled promise rejections | unhandledrejection | Error | | React render/lifecycle errors | <ErrorBoundary> / Next error.tsx | Error | | Failed resource loads | error (capture phase) | Warning | | Manual captureException | you | Error (override via hint) | | Manual captureMessage | you | your choice | | console.error / console.warn | breadcrumb only (not an event) | – |

Each event carries: message, stack, exception type, error.cause chain, the current route, user agent, viewport, release + environment, your tags, and the recent breadcrumb trail (all under the dashboard's Additional data). The same error hitting multiple hooks is de-duplicated into a single report.


9. Security & privacy (read this)

  • The API key is public. Use a write-only / ingest-only key so a leaked key can only submit errors — never read or manage data. (Ask your CherryBoard admin to scope the key to error ingest.)

  • CORS & rate limiting live on the backend. The ingest endpoint should allow your app's origin(s) and rate-limit per key to blunt abuse of the public key.

  • PII scrubbing is on by defaultAuthorization/tokens/cookies, emails, and sensitive query params/keys (password, secret, token, …) are redacted before anything leaves the browser. Add your own rules via beforeSend. The SDK never sends request/response bodies or form values.

  • Error tracking writes to localStorage by default. offlineStorage (default true) persists undelivered events under cherryboard:queue:v1 so they survive a reload and are retried later. Storing data on a visitor's device is covered by ePrivacy / "cookie law" in the EU, and analytics-style storage is generally not treated as strictly necessary — so under a consent banner this belongs behind consent, or set offlineStorage: false to make the SDK storage-free. The trade is that events buffered when a tab closes are lost instead of retried.

    Performance tracking (§14) stores nothing on the device either way.

  • Source maps: see Readable stack traces. Don't serve .map files publicly — upload them instead.

  • SSR-safe: the core touches no browser globals at import time; the React entry is marked "use client". Don't call the SDK from "use server" modules — it's for the browser.


10. Troubleshooting

  • No errors appear. Confirm init ran (set debug: true), the key/URL are correct, and the browser Network tab shows a POST …/api/v1/errors/batch. A 401/403 means a bad/expired key; a CORS error means the backend must allow your origin.
  • "Script error." with no stack. A cross-origin script threw. Add crossorigin="anonymous" to the script tag and Access-Control-Allow-Origin on the asset host. The SDK already drops bare Script error. noise.
  • Duplicate reports. Shouldn't happen (built-in dedupe), but avoid manually calling captureException for an error your ErrorBoundary already handles.
  • window is not defined during build. You imported the SDK into server-only code. Call it from client components / instrumentation-client.ts only.
  • Events lost on tab close. Handled — the SDK flushes via keepalive on pagehide/ visibility change, and persists to an offline queue that drains when you're back online.

11. Readable stack traces (source maps)

Production stacks are minified (main.4f2c1b.js:1:24817). Upload your build's source maps and CherryBoard resolves them server-side into real files, lines and function names — shown on the issue page as original sources.

Two things must line up: the release you pass to init() and the --release you upload with. If they differ, nothing is symbolicated (silently — it's recorded as "not applicable", not an error).

Add to CI, after the build:

npx cherryboard-upload-sourcemaps \
  --dir .next/static \
  --url-prefix /_next/static \
  --release "$GIT_SHA" \
  --api-url https://<your-cherryboard-api-host> \
  --api-key "$CHERRYBOARD_UPLOAD_KEY"
  • Use a General (server) key — never the browser key. The write-only browser scope deliberately cannot upload build artifacts.
  • --dry-run lists what would upload without sending anything.
  • Uploading the same file for a release again replaces it, so re-running a build is safe.
  • Maps are stored privately and expire after 90 days.

Next.js needs productionBrowserSourceMaps: true in next.config.ts to emit them. Prefer not serving the .map files to the public — uploading is enough.

12. Server-side errors (Next.js)

Browser capture never sees RSC render, route handler or server action errors. Catch them in instrumentation.ts:

// instrumentation.ts
import { init, captureRequestError } from '@cherrypeak-org/cherryboard-web';

export function register() {
  init({
    apiKey: process.env.CHERRYBOARD_KEY!,   // server-side key
    apiUrl: process.env.CHERRYBOARD_URL!,
    environment: process.env.NEXT_PUBLIC_ENV ?? 'production',
    release: process.env.NEXT_PUBLIC_COMMIT_SHA,
  });
}

export const onRequestError = captureRequestError;

The core runs fine outside the browser: it installs no window handlers there and delivers over fetch.

13. Why didn't my error show up?

Events can be dropped on purpose (sampling, dedupe, filters) — ask the SDK:

import { getDiscardedEvents } from '@cherrypeak-org/cherryboard-web';
console.log(getDiscardedEvents());
// { deduped: 3, sampled: 1, rate_limited: 0, filtered: 0, send_failed: 0 }

API summary

// Core — @cherrypeak-org/cherryboard-web
init(config): CherryBoardClient
captureException(error, hint?): void
captureMessage(message, severity?, hint?): void
addBreadcrumb(crumb): void
setUser(user | null): void
setTag(key, value): void
setContext(key, value): void
flush(): Promise<void>
close(): void
getClient(): CherryBoardClient | null
getDiscardedEvents(): Record<string, number>
captureRequestError(error, request?, context?)   // Next.js instrumentation.ts

// CLI
cherryboard-upload-sourcemaps --dir <dir> --release <id> --api-url <url> --api-key <key>

// React — @cherrypeak-org/cherryboard-web/react
<CherryBoardProvider config withBoundary? fallback?>
<ErrorBoundary fallback? onError? resetKeys?>
useCaptureError(): (error, hint?) => void
useCherryBoard(): CherryBoardClient | null
captureRouteError(error): void   // for Next error.tsx / global-error.tsx

14. Performance & visits

Off by default. One option turns on API request timings and page-view statistics:

init({
  apiKey: process.env.NEXT_PUBLIC_CHERRYBOARD_KEY!,
  apiUrl: process.env.NEXT_PUBLIC_CHERRYBOARD_URL!,
  trackPerformance: true,
});

Timings appear on the environment page in the dashboard within a few minutes.

What it costs the page

Effectively nothing. Timings come from PerformanceObserver, so the browser is already recording them and nothing is added to the request path. No fetch patching is involved, which also means no conflict with other libraries that wrap fetch themselves.

Rollups are aggregated in memory and posted once a minute. A page making 200 API calls sends one small summary, not 200 events — payload size tracks the number of distinct routes, not traffic.

What is recorded

| Kept | Not kept | |---|---| | Route templates — /api/orders/:id | Concrete URLs; identifiers collapse to :id before anything is recorded | | Counts, durations, error counts | Query strings — dropped entirely, since they carry tokens and emails | | Page views and visits | Anything identifying a visitor |

Performance tracking writes nothing to the visitor's device and stores no identifier. "Visits" counts document loads, not people — a refresh or a second tab is another visit.

Counting people (v1.3.0+)

If you call setUser({ id }), that id is also sent with performance rollups so the dashboard can report distinct signed-in users. The server hashes it on arrival, salted per project, and never stores the value you sent. Nothing is written to the visitor's device either way.

Two things worth knowing before enabling this:

  • It counts signed-in traffic only. Anonymous visitors have no id, and inventing one would mean device storage.
  • The hash is pseudonymised, not anonymous — anyone holding a user id can hash it and find the rows. It remains personal data, ages out with the 60-day retention, and counts towards a subject request.

Because this uses an identifier you already send for error tracking, it adds no new data — but it is a new purpose, and under GDPR that has to be disclosed in your privacy notice on its own footing. To opt out, don't call setUser, or strip userId in beforeSend; the rest of the performance data is unaffected.

Note this is not true of error tracking, which persists undelivered events to localStorage by default. See §9.

Reading the numbers

Percentiles come from latency histograms, so they are accurate to the nearest bucket rather than exact. Watch p95 — it is the experience of your slowest 1 in 20 requests, which averages hide.

Cross-origin timings require Timing-Allow-Origin on the responding server, or the browser reports zero.

License

Apache License 2.0 — see LICENSE.

Free to use, modify and redistribute, including commercially. It includes an express patent grant, and asks that you keep the copyright notice and state any changes you make.