Server-side monitoring

The client SDK sees what breaks in the browser. This sees what breaks behind it, including the case where your app is too broken to report anything at all.

Coding Agent Prompt for Server Monitoring

Copy & paste this prompt into Cursor, Claude, ChatGPT, Copilot, or Antigravity to add server-side monitoring to your Next.js app.

Add Cluebase server-side monitoring to this Next.js project, so backend errors reach the team and the affected user still gets the rescue widget:

1. This is a SEPARATE credential from the publishable key. APPEND the project's SECRET key to the project's env file, normally `.env.local`:
   `CLUEBASE_SECRET_KEY=cb_sk_your_secret_key_here`
   Check whether that file already exists first and append a single line to it. NEVER create it fresh over the top of an existing one and never rewrite its contents: `.env.local` is where a project keeps its database URLs, auth secrets and API keys, and replacing it would destroy the user's local configuration. If the project keeps its variables somewhere else (`.env`, `.env.development`), append to that instead of introducing a new file.
   You cannot generate this from code. Tell the user to copy it from the "Secret key" section of their project settings in the Cluebase dashboard, and note that the full value is only shown when the key is issued or rotated, so they must copy it at that moment.
   CRITICAL: never prefix this one with `NEXT_PUBLIC_`. The publishable key (`cb_pk_`) is meant to be readable in the browser; the secret key is not, and a `NEXT_PUBLIC_` prefix would ship it in the client bundle for anyone to read. If you are copying the shape of the existing publishable-key line, do not copy its prefix.
   Do not go looking for a place to reference this variable in code, and do not add one. Nothing you write names it. `register` and `onRequestError` from `cluebase-next/server` read `process.env.CLUEBASE_SECRET_KEY` themselves, which is why it appears in none of the code below.
   That also means a missing key fails silently: if it is absent, misspelled, or `NEXT_PUBLIC_`-prefixed, both functions return immediately. The build passes, the hooks still run, and nothing is ever reported or heartbeated. From SDK 1.0.1 onward the server logs a one-time warning, `[Cluebase] CLUEBASE_SECRET_KEY is not set`, so tell the user to check their server logs to confirm the key is actually being read.

2. Check the Next.js version in `package.json` before editing. `onRequestError` requires Next 15 or newer. If this project is on Next 14 or older, stop and tell the user to upgrade first: the rest of these steps will not work, and adding them silently would look integrated while capturing nothing.

3. Add the instrumentation hook.
   FIRST check whether `instrumentation.ts` (or `.js`) already exists, in the project root or in `src/` if the project uses a `src` directory. This file is shared: OpenTelemetry, Sentry and others live in it too.
   - If it does NOT exist, create it with exactly:
     `export { register, onRequestError } from 'cluebase-next/server';`
   - If it DOES exist, MERGE, never overwrite. Import Cluebase's functions under an alias and call them from the existing exports, preserving whatever is already there:
     `import { register as cluebaseRegister, onRequestError as cluebaseOnRequestError } from 'cluebase-next/server';`
     then call `cluebaseRegister()` inside the existing `register()`, and `await cluebaseOnRequestError(error, request, context)` inside the existing `onRequestError`. Overwriting this file would silently delete the user's existing observability setup.
   `onRequestError` is Next's official hook for server errors, so this captures failed server renders, route handlers and server actions with no per-call-site changes. `register` starts the heartbeat described in step 5.

4. Add the error mount point, and pick the right one for this project's router:
   - App Router (there is an `app/` directory): the file is `app/global-error.tsx`.
   - Pages Router (there is a `pages/` directory and no `app/`): there is no `global-error` equivalent, so skip this step entirely. Server errors are still captured by step 3; only the in-browser widget for a failed server render is unavailable. Say so in your summary rather than inventing a file.
   For App Router, if `app/global-error.tsx` does NOT already exist, create it as:
   `'use client';`
   `import { CluebaseGlobalError } from 'cluebase-next';`
   `export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {`
   `  return <CluebaseGlobalError error={error} reset={reset} apiKey={process.env.NEXT_PUBLIC_CLUEBASE_API_KEY ?? ''} />;`
   `}`
   Pass the PUBLISHABLE key here (the same `NEXT_PUBLIC_` one the provider already uses), not the secret key. This file runs in the browser.
   If it DOES exist, do not replace the user's error UI. Keep their component and render `<CluebaseGlobalError error={error} reset={reset} apiKey={...}>` around their existing markup as children, so their design is preserved and Cluebase still reports.
   Two things that break this file if you write your own version: it must be a Client Component (`'use client'` at the top), and because Next swaps out the root layout here it must render its own `<html>` and `<body>` tags.
   ALSO search the app directory for existing `error.tsx` files. Next only falls back to `global-error.tsx` when nothing closer catches, so any segment with its own `error.tsx` handles its own failures and global-error never runs for them. For each one found, add the inline variant so that segment is not silent for the user: import `{ CluebaseError }` (not CluebaseGlobalError) and render `<CluebaseError error={error} retry={retry} apiKey={process.env.NEXT_PUBLIC_CLUEBASE_API_KEY ?? ''} />`, or wrap their existing UI as its children. CluebaseError renders inline and must NOT have `<html>`/`<body>` tags.
   Next 16 names the retry prop `retry`; older versions call it `reset`. Both components accept either, so pass whichever the installed version provides.
   Why it matters: when a server render throws, the provider is inside the tree that just died, so it never mounts. This is the only client mount point Next gives you in that situation. In production Next hides the real error from the browser and sends only a digest; this component forwards that digest so Cluebase can match it to what the server already reported and explain the real cause instead of "something went wrong".

5. The heartbeat is already included by `register` in step 3 and needs no extra code. It sends a lightweight signal about once a minute, and Cluebase alerts the team when those signals stop, which is how a crashed or unbootable process gets noticed at all: a dead process cannot report its own errors. It is part of server-side monitoring on every plan, including Free. Do not add a cron job, a health endpoint, or a polling script for this.

6. Do not change anything on the client. `CluebaseProvider`, the publishable key and the existing setup all stay exactly as they are; server monitoring runs alongside them, it does not replace them. Failed `fetch` calls are already captured automatically by the provider, so do not wrap call sites by hand and do not add try/catch around fetch for Cluebase's benefit.

7. Verify, then summarise. Confirm the app still builds. In your final summary state plainly: which files you created or merged into, that the user must add `CLUEBASE_SECRET_KEY` from their dashboard for any of this to work, and whether you skipped step 4 because this is a Pages Router project.

Note: If you have web search capability, visit https://cluebase.dev/docs/server-monitoring for detailed documentation.

Error reporting from inside your app has a blind spot it cannot fix: something has to be alive to do the reporting. A failed deploy, a crash loop, an out-of-memory kill or a database that is unreachable at boot all produce the same thing on your dashboard, which is nothing at all. Server monitoring closes that gap from two directions: server errorsget captured through Next's own hook, and a heartbeat tells us when the process stops answering.

Setup

Two files, and a key. Everything on the client stays exactly as it is.

Server monitoring uses your secret key (cb_sk_), not the publishable one your provider already uses. Find it under Secret key in your project settings, where you can also rotate it. The full value is shown only when it is issued or rotated, so copy it then.

Terminal
# .env.local
CLUEBASE_SECRET_KEY=cb_sk_your_secret_key_here
Never give this one a NEXT_PUBLIC_ prefix. Your publishable key (cb_pk_) is designed to be readable in the browser. The secret key is not, and prefixing it would ship it in your client bundle for anyone to read. It is the credential that lets anything report as your project.

You will not see this variable named anywhere in the code below, and you do not need to wire it up. register and onRequestError read process.env.CLUEBASE_SECRET_KEY themselves when Next calls them.

Which also means a missing key fails quietly. If the variable is absent, misspelled, or NEXT_PUBLIC_-prefixed, both functions return straight away: your build passes, Next still calls the hooks, and nothing is ever reported or heartbeated. From SDK 1.0.1 the server logs a one-time [Cluebase] CLUEBASE_SECRET_KEY is not set warning, so check your server logs to confirm the key is being read.
typescript
// instrumentation.ts
export { register, onRequestError } from 'cluebase-next/server';

onRequestError is the hook Next.js provides for exactly this, so it covers failed server renders, route handlers and server actions without you touching a single call site. register starts the heartbeat.

tsx
// app/global-error.tsx
'use client';
import { CluebaseGlobalError } from 'cluebase-next';
export default function GlobalError({
error,
retry,
}: {
error: Error & { digest?: string };
retry: () => void;
}) {
return (
<CluebaseGlobalError
error={error}
retry={retry}
apiKey={process.env.NEXT_PUBLIC_CLUEBASE_API_KEY ?? ''}
/>
);
}

Next 16 names that prop retry; older versions call it reset. The component accepts either, so pass whichever your version gives you.

That is your publishable key, the same one the provider already uses. This file runs in the browser, so the secret key must never appear here.

That second file is App Router only. On the Pages Router there is no global-error equivalent, so skip it: server errors are still captured by instrumentation.ts exactly the same way, and what you lose is only the in-browser widget for a failed server render. Everything the client SDK already catches, including failed requests, is unaffected.

If you already use error.tsx

Next only falls back to global-error.tsx when nothing closer catches. A route segment with its own error.tsx handles its own failures, and global-error.tsx never runs for them. So if you haveerror.tsx files, add CluebaseError to them too, or those segments stay silent for the user.

tsx
// app/dashboard/error.tsx
'use client';
import { CluebaseError } from 'cluebase-next';
export default function Error({
error,
retry,
}: {
error: Error & { digest?: string };
retry: () => void;
}) {
return (
<CluebaseError
error={error}
retry={retry}
apiKey={process.env.NEXT_PUBLIC_CLUEBASE_API_KEY ?? ''}
/>
);
}

Same component, same reporting, one difference: CluebaseError renders inline, because only global-error.tsx replaces the root layout and needs its own <html> and <body>. Both accept your own UI as children if you would rather keep your existing design and only add the reporting.

Why the second file matters

When a server render throws, your provider is inside the tree that just died, so it never mounts and the widget cannot appear. global-error.tsxis the one client mount point Next gives you in that situation. Without it, a user hitting a broken page sees Next's bare error screen and nothing else.

In production Next deliberately hides the real error from the browser and sends only a digest hash, which is why an error page can feel so uninformative. Cluebase forwards that digest and matches it to what your server already reported, so the agent can explain what actually happened instead of guessing.

Writing your own global-error.tsx instead of re-exporting ours is fine, but it replaces your root layout, so it has to render its own <html> and <body> tags.

The heartbeat

A dead process cannot tell you it died. So instead of waiting for errors, your app sends a small signal about once a minute, and we alert your team when those signals stop. That is what catches the failures nothing else can see: the crash loop, the bad deploy, the region outage.

On a long-running server it runs on a timer. On serverless there is no persistent process to hold one, so it rides your request traffic instead. That means a genuinely quiet app can go silent without being broken, which is why the alert says no signal from your server rather than claiming it is down.

The heartbeat is part of server-side monitoring on every plan, including Free. There is nothing to enable: your project starts being watched the first time a signal arrives.

What each part covers

FailureCaught by
Server render throwsonRequestError + global-error.tsx, widget shown
Route handler or server action failsonRequestError, plus the client if a request saw it
An API call from the browser returns 500The client SDK, automatically
Cron job or webhook failsonRequestError, team alerted, no widget (no user present)
Process dead, crash loop, bad deployThe heartbeat

One thing nothing here can cover: if your server is hard down and serves no HTML at all, no JavaScript runs, so no widget can appear. Your team still gets told through the heartbeat.

Failed requests are already covered

You do not need to wrap your fetch calls. The provider watches them for you, so a request that returns 500 or fails outright reaches Cluebase without any change at your call sites. If you would rather it did not, pass captureFetch={false} to CluebaseProvider.

Server errors count toward error capture, which is unlimited. A conversation is only spent when a real person is actually shown the widget, so a backend error that nobody hit costs nothing.