Docs · Errors

Error capture

One dependency, or one script tag, and “what broke” has data: uncaught errors and unhandled rejections from the browser and the server, attached to the incident with the release that caused them.

What it sends, and what it never does

Uncaught errors, unhandled rejections and failed requests. Never form values or request bodies.

Sampling 50%, 60 events per minute per app, 2.1 KB. Messages are redacted before they leave the page. It cannot throw into your app or block it.

Your keys are under Integrations › Error capture in your workspace: a public key for the browser, matched to an application by the page’s hostname, and one key per application for a server, which has no page to match on.

The package

Not on npm yet. The package is built and its install is documented below, but the name does not resolve until it is published. Use the script tag until it does.

Prefer the dependency where you can: pasted code never gets the fix, and an agency with thirty applications would be maintaining thirty copies.

Terminal
pnpm add @keelnest/capture
npm install @keelnest/capture
yarn add @keelnest/capture
bun add @keelnest/capture

Next.js

The server: rendering, Route Handlers and Server Actions.
instrumentation.ts
export { register, onRequestError } from "@keelnest/capture/next";
The browser, before React hydrates.
instrumentation-client.ts
import { init } from "@keelnest/capture";

init({
  key: process.env.NEXT_PUBLIC_KEELNEST_KEY,
  host: process.env.NEXT_PUBLIC_KEELNEST_HOST,
});

Vite, Astro or Node

Once, before the code that might throw.
Your entry file
import { init } from "@keelnest/capture";

init({ key: KEELNEST_KEY, host: KEELNEST_HOST });

Cloudflare Workers

Edge runtimes have no global error event, so the handler carries the reporting itself.
worker.ts
import { wrap } from "@keelnest/capture";

export default {
  fetch: wrap(async (request, env) => {
    /* … */
  }),
};

The keys

The browser key is public by design; the server key names one application.
.env.local
# The browser half: public by design, matched to an app by the page's hostname.
NEXT_PUBLIC_KEELNEST_KEY=pk_live_…
NEXT_PUBLIC_KEELNEST_HOST=https://keelnest.com

# The server half: this key names one application. Get it under Server.
KEELNEST_KEY=pk_live_…
KEELNEST_HOST=https://keelnest.com

The script tag

For an application you cannot add a dependency to — a site a client edits by hand, a builder that owns package.json, a page inside a CMS. Paste it in the <head> of the site; events are attributed to the application whose production URL matches the page.

<head>
<script async src="https://keelnest.com/e.js" data-key="pk_live_…" data-sample="0.5"></script>

A server without the package

A process has no page, so it sends its application’s own key. Import once at startup — in Next.js, instrumentation.ts.

keelnest.ts
// Keelnest server error capture. Import once at startup
// (Next.js: instrumentation.ts — otherwise your server entry file).
const KEY = process.env.KEELNEST_KEY;
const URL = "https://keelnest.com/api/ingest/errors";

export async function reportError(error, kind = "error") {
  if (!KEY) return;
  try {
    await fetch(URL, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        key: KEY,
        events: [{
          runtime: "server",
          kind,
          message: String(error?.message ?? error),
          stack: String(error?.stack ?? ""),
          at: new Date().toISOString(),
        }],
      }),
    });
  } catch {
    // Reporting an error must never become one.
  }
}

process.on("uncaughtException", (e) => reportError(e, "uncaught"));
process.on("unhandledRejection", (e) => reportError(e, "unhandled"));

Prompts for a builder

If Lovable, Bolt, Cursor or Claude Code builds the app, hand it one of these instead of the steps above.

Prompt · package
Add Keelnest error capture to my app as a dependency: install @keelnest/capture and wire it up. If this is a Next.js app, re-export register and onRequestError from "@keelnest/capture/next" in instrumentation.ts, and call init({ key: process.env.NEXT_PUBLIC_KEELNEST_KEY, host: process.env.NEXT_PUBLIC_KEELNEST_HOST }) in instrumentation-client.ts. Otherwise call init({ key, host }) once at the top of the entry file, and on Cloudflare Workers wrap the fetch handler with wrap() from the same package. Set NEXT_PUBLIC_KEELNEST_KEY=pk_live_… and NEXT_PUBLIC_KEELNEST_HOST=https://keelnest.com in the environment; leave the server-side KEELNEST_KEY for me to fill in. Do not change anything else, and do not add any other monitoring.
Prompt · script tag
Add the Keelnest error capture snippet to my app. Put this script tag in the <head> of the root HTML (or the root layout): <script async src="https://keelnest.com/e.js" data-key="pk_live_…" data-sample="0.5"></script>. Do not change anything else. It must not capture form values or request bodies.

Uninstalling

Remove the dependency, or the script tag, from the application. Events stop with the next page load or the next deploy; the app’s history stays. The package itself is @keelnest/capture, MIT-licensed, with no dependencies of its own. All docs.