ReactThe two exports, with the context fields taken from the shipped types
import { useState } from "react";
const FILE = `// instrumentation.ts — project root, or src/
import type { Instrumentation } from "next";
export async function register() {
// Runs ONCE when the server process starts, before any request is handled.
// That timing is the point: an APM agent patches modules like http and your
// database driver as they are imported, so it must run before they are.
if (process.env.NEXT_RUNTIME === "nodejs") {
// Most tracing SDKs are Node-only, so import conditionally or the edge
// build breaks.
await import("./instrumentation.node");
}
}
export const onRequestError: Instrumentation.onRequestError = async (
error, // unknown
request, // { path, method, headers }
context, // the useful one — see the table below
) => {
await sendToMonitoring({
message: String(error),
path: request.path,
method: request.method,
router: context.routerKind,
route: context.routePath,
type: context.routeType,
source: context.renderSource,
revalidate: context.revalidateReason,
});
};`;
const CONTEXT_FIELDS = [
["routerKind", "'Pages Router' | 'App Router'", "which router produced it"],
["routePath", "string", "the route that failed"],
["routeType", "'render' | 'route' | 'action' | 'proxy'", "page render, route handler, Server Action, or proxy"],
["renderSource", "'react-server-components' | 'react-server-components-payload' | 'server-rendering'", "only present for renders"],
["revalidateReason", "'on-demand' | 'stale' | undefined", "why a revalidation was running"],
];
const SCENARIOS = [
{ label: "a Server Component threw", routeType: "render", renderSource: "react-server-components", caught: true },
{ label: "a route handler threw", routeType: "route", renderSource: "—", caught: false },
{ label: "a Server Action failed", routeType: "action", renderSource: "—", caught: false },
{ label: "a client render error", routeType: "(not server-side)", renderSource: "—", caught: true },
];
export default function App() {
const [i, setI] = useState(0);
const s = SCENARIOS[i];
return (
<div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6, maxWidth: 640 }}>
<pre style={{ background: "#f6f6f8", border: "1px solid #ddd", borderRadius: 8,
padding: 12, fontSize: 12, overflowX: "auto", whiteSpace: "pre-wrap" }}>
{FILE}
</pre>
<h4 style={{ margin: "14px 0 6px" }}>The context argument</h4>
<table style={{ width: "100%", fontSize: 12, borderCollapse: "collapse" }}>
<tbody>
{CONTEXT_FIELDS.map(([name, type, note]) => (
<tr key={name} style={{ borderBottom: "1px solid #eee" }}>
<td style={{ padding: "3px 8px 3px 0", whiteSpace: "nowrap" }}><code>{name}</code></td>
<td style={{ padding: "3px 8px 3px 0", color: "#4f46e5" }}><code>{type}</code></td>
<td style={{ color: "#666" }}>{note}</td>
</tr>
))}
</tbody>
</table>
<h4 style={{ margin: "14px 0 6px" }}>Who sees what</h4>
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 8 }}>
{SCENARIOS.map((sc, n) => (
<button key={sc.label} onClick={() => setI(n)} style={{ fontWeight: n === i ? "bold" : "normal", fontSize: 12 }}>
{sc.label}
</button>
))}
</div>
<div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, fontSize: 13 }}>
<div>error boundary shows a fallback: <strong style={{ color: s.caught ? "#161" : "#a33" }}>{s.caught ? "yes" : "no"}</strong></div>
<div>
onRequestError fires:{" "}
<strong style={{ color: s.routeType === "(not server-side)" ? "#a33" : "#161" }}>
{s.routeType === "(not server-side)" ? "no — server-side only" : "yes"}
</strong>
</div>
<div style={{ color: "#666" }}>
context.routeType = <code>{s.routeType}</code> · renderSource = <code>{s.renderSource}</code>
</div>
</div>
<p style={{ fontSize: 13, color: "#666" }}>
The two middle scenarios are the reason this hook exists: a route
handler and a Server Action fail outside any render, so no boundary is
in the window and the user-facing mechanism reports nothing at all.
</p>
</div>
);
}