From 168dbc930808acedb8c7eafa39754ad7845a4446 Mon Sep 17 00:00:00 2001 From: Sagnik Ghosh Date: Fri, 24 Jul 2026 02:00:41 +0530 Subject: [PATCH] fix(next): split runtime-next into server and client entry points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single barrel export mixed @autter/runtime-node (Node OTel SDK) with browser re-exports and the React error boundary, so the documented client component import pulled the OTel SDK into the browser bundle and failed the Next.js build (fs cannot be resolved). - Root / ./server entry: registerAutter, createAutterRelayRoute, captureServerException/Message — server-only code paths. - ./client entry: browser tracker API + AutterErrorBoundary, built with a "use client" banner and no Node imports. - Docs updated to import client pieces from @autter/runtime-next/client. Generated-By: PostHog Code Task-Id: 74ac2be4-ebb7-4ac4-8db4-62b20460f276 --- docs/GETTING-STARTED.md | 2 +- docs/INTEGRATIONS.md | 2 +- packages/runtime-next/README.md | 21 +++++++-- packages/runtime-next/package.json | 12 ++++- packages/runtime-next/src/client.ts | 25 ++++++++++ packages/runtime-next/src/index.ts | 68 ++-------------------------- packages/runtime-next/src/server.ts | 52 +++++++++++++++++++++ packages/runtime-next/tsup.config.ts | 21 +++++++++ 8 files changed, 133 insertions(+), 70 deletions(-) create mode 100644 packages/runtime-next/src/client.ts create mode 100644 packages/runtime-next/src/server.ts create mode 100644 packages/runtime-next/tsup.config.ts diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index e518e5c..7394266 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -172,7 +172,7 @@ setUser("u_8f2k1"); // opaque id — never a ``` React render errors don't reach `window.onerror` — add the boundary -(exported from `@autter/runtime-next`, works in any React app): +(exported from `@autter/runtime-next/client`, works in any React app): ```tsx }> diff --git a/docs/INTEGRATIONS.md b/docs/INTEGRATIONS.md index 3014b35..14f8f10 100644 --- a/docs/INTEGRATIONS.md +++ b/docs/INTEGRATIONS.md @@ -39,7 +39,7 @@ initAutterBrowser({ ``` React render errors: wrap with `` (from -`@autter/runtime-next`, works in any React app). Vue/Svelte/Angular: call +`@autter/runtime-next/client`, works in any React app). Vue/Svelte/Angular: call `captureException(err)` from the framework's error hook (`app.config.errorHandler`, `handleError`, `ErrorHandler`). diff --git a/packages/runtime-next/README.md b/packages/runtime-next/README.md index 4c1b155..ccc4a1a 100644 --- a/packages/runtime-next/README.md +++ b/packages/runtime-next/README.md @@ -9,7 +9,15 @@ same-origin relay route, and a React error boundary. npm install @autter/runtime-next ``` -Set `AUTTER_RUNTIME_KEY` in your environment. Then three small files: +Set `AUTTER_RUNTIME_KEY` in your environment. Then three small files. + +The package has two entry points — this split is what keeps the Node +OpenTelemetry SDK out of your browser bundle: + +- `@autter/runtime-next` (or `@autter/runtime-next/server`) — server only: + `instrumentation.ts`, route handlers, server components. +- `@autter/runtime-next/client` — client components: browser tracker + + error boundary. Never imports Node code. **1. `instrumentation.ts`** — server tracing/errors: @@ -41,7 +49,7 @@ export const { POST } = createAutterRelayRoute({ ```tsx "use client"; -import { initAutterBrowser, AutterErrorBoundary } from "@autter/runtime-next"; +import { initAutterBrowser, AutterErrorBoundary } from "@autter/runtime-next/client"; initAutterBrowser({ endpoint: "/api/autter-runtime", @@ -62,5 +70,10 @@ React render errors don't reach `window.onerror` — the boundary reports them via `captureException` and then renders your fallback. Also re-exported for convenience: `captureException`, `captureMessage`, -`trackEvent`, `setUser`, `setContext`, `flush` (browser) and -`captureServerException`, `captureServerMessage` (server). +`trackEvent`, `setUser`, `setContext`, `flush` (from +`@autter/runtime-next/client`) and `captureServerException`, +`captureServerMessage` (from the root / `@autter/runtime-next/server`). + +> Importing the root entry from a client component pulls the Node OTel SDK +> into the browser bundle and fails the build (`fs` cannot be resolved). +> Client code must always use `@autter/runtime-next/client`. diff --git a/packages/runtime-next/package.json b/packages/runtime-next/package.json index 6058c40..73c13a7 100644 --- a/packages/runtime-next/package.json +++ b/packages/runtime-next/package.json @@ -12,6 +12,16 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" + }, + "./server": { + "types": "./dist/server.d.ts", + "import": "./dist/server.js", + "require": "./dist/server.cjs" + }, + "./client": { + "types": "./dist/client.d.ts", + "import": "./dist/client.js", + "require": "./dist/client.cjs" } }, "files": [ @@ -23,7 +33,7 @@ "directory": "packages/runtime-next" }, "scripts": { - "build": "tsup src/index.ts --format esm,cjs --dts --target node20 --external react --clean" + "build": "tsup" }, "dependencies": { "@autter/runtime-browser": "^1.0.0", diff --git a/packages/runtime-next/src/client.ts b/packages/runtime-next/src/client.ts new file mode 100644 index 0000000..21e2a5d --- /dev/null +++ b/packages/runtime-next/src/client.ts @@ -0,0 +1,25 @@ +/** + * Client half of @autter/runtime-next — import from `@autter/runtime-next/client` + * in client components only. Contains zero Node code, so nothing OTel-shaped + * ever reaches the browser bundle. + * + * "use client"; + * import { initAutterBrowser, AutterErrorBoundary } from "@autter/runtime-next/client"; + * + * initAutterBrowser({ endpoint: "/api/autter-runtime", service: "web-app" }); + */ + +export { + initAutterBrowser, + captureException, + captureMessage, + trackEvent, + setUser, + setContext, + flush, + type AutterSeverity, +} from "@autter/runtime-browser"; +export { + AutterErrorBoundary, + type AutterErrorBoundaryProps, +} from "./error-boundary.js"; diff --git a/packages/runtime-next/src/index.ts b/packages/runtime-next/src/index.ts index 87ce212..8fd16ee 100644 --- a/packages/runtime-next/src/index.ts +++ b/packages/runtime-next/src/index.ts @@ -1,68 +1,10 @@ /** * @autter/runtime-next — one-command Autter Runtime for Next.js. * - * 1. `instrumentation.ts` (server tracing): - * - * export async function register() { - * if (process.env.NEXT_RUNTIME === "nodejs") { - * const { registerAutter } = await import("@autter/runtime-next"); - * registerAutter({ - * apiKey: process.env.AUTTER_RUNTIME_KEY!, - * service: "web-app", - * release: process.env.GIT_SHA, - * }); - * } - * } - * - * 2. `app/api/autter-runtime/route.ts` (browser relay): - * - * import { createAutterRelayRoute } from "@autter/runtime-next"; - * export const { POST } = createAutterRelayRoute({ - * apiKey: process.env.AUTTER_RUNTIME_KEY!, - * }); - * - * 3. A client component (browser tracker + boundary): - * - * "use client"; - * import { initAutterBrowser, AutterErrorBoundary } from "@autter/runtime-next"; - * initAutterBrowser({ endpoint: "/api/autter-runtime", service: "web-app" }); + * The root entry is SERVER-ONLY (`instrumentation.ts`, route handlers). + * Client components must import from `@autter/runtime-next/client` — + * importing the root from client code would pull the Node OpenTelemetry + * SDK into the browser bundle and break the build. */ -import { - createBrowserRelayFetchHandler, - initAutterServer, - type AutterServer, - type AutterServerOptions, - type RelayOptions, -} from "@autter/runtime-node"; - -export { - initAutterBrowser, - captureException, - captureMessage, - trackEvent, - setUser, - setContext, - flush, - type AutterSeverity, -} from "@autter/runtime-browser"; -export { - AutterErrorBoundary, - type AutterErrorBoundaryProps, -} from "./error-boundary.js"; -export { - captureException as captureServerException, - captureMessage as captureServerMessage, -} from "@autter/runtime-node"; - -/** Server OTel init for Next.js `instrumentation.ts`. */ -export function registerAutter(options: AutterServerOptions): AutterServer { - return initAutterServer(options); -} - -/** App Router relay route: `export const { POST } = createAutterRelayRoute({...})`. */ -export function createAutterRelayRoute(options: RelayOptions): { - POST: (request: Request) => Promise; -} { - return { POST: createBrowserRelayFetchHandler(options) }; -} +export * from "./server.js"; diff --git a/packages/runtime-next/src/server.ts b/packages/runtime-next/src/server.ts new file mode 100644 index 0000000..c1e23a5 --- /dev/null +++ b/packages/runtime-next/src/server.ts @@ -0,0 +1,52 @@ +/** + * Server half of @autter/runtime-next. Safe to import anywhere Node runs: + * `instrumentation.ts`, route handlers, server components, server actions. + * + * 1. `instrumentation.ts` (server tracing): + * + * export async function register() { + * if (process.env.NEXT_RUNTIME === "nodejs") { + * const { registerAutter } = await import("@autter/runtime-next"); + * registerAutter({ + * apiKey: process.env.AUTTER_RUNTIME_KEY!, + * service: "web-app", + * release: process.env.GIT_SHA, + * }); + * } + * } + * + * 2. `app/api/autter-runtime/route.ts` (browser relay): + * + * import { createAutterRelayRoute } from "@autter/runtime-next"; + * export const { POST } = createAutterRelayRoute({ + * apiKey: process.env.AUTTER_RUNTIME_KEY!, + * }); + * + * Browser tracker + error boundary live in `@autter/runtime-next/client`. + */ + +import { + createBrowserRelayFetchHandler, + initAutterServer, + type AutterServer, + type AutterServerOptions, + type RelayOptions, +} from "@autter/runtime-node"; + +export { + captureException as captureServerException, + captureMessage as captureServerMessage, +} from "@autter/runtime-node"; +export type { AutterServer, AutterServerOptions, RelayOptions }; + +/** Server OTel init for Next.js `instrumentation.ts`. */ +export function registerAutter(options: AutterServerOptions): AutterServer { + return initAutterServer(options); +} + +/** App Router relay route: `export const { POST } = createAutterRelayRoute({...})`. */ +export function createAutterRelayRoute(options: RelayOptions): { + POST: (request: Request) => Promise; +} { + return { POST: createBrowserRelayFetchHandler(options) }; +} diff --git a/packages/runtime-next/tsup.config.ts b/packages/runtime-next/tsup.config.ts new file mode 100644 index 0000000..92a0731 --- /dev/null +++ b/packages/runtime-next/tsup.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "tsup"; + +export default defineConfig([ + { + entry: { index: "src/index.ts", server: "src/server.ts" }, + format: ["esm", "cjs"], + dts: true, + target: "node20", + clean: true, + }, + { + entry: { client: "src/client.ts" }, + format: ["esm", "cjs"], + dts: true, + target: "es2019", + external: ["react"], + // Next.js needs the directive on the bundled file, or the error + // boundary can't be used from server-component trees. + banner: { js: '"use client";' }, + }, +]);