e-volv
Docs menu

Browser, front ends and edge

A web front end has two halves. The server half (Next.js, Remix, SvelteKit, Nuxt) is a Node.js process and uses the Node.js SDK today. The browser half needs a package that runs without Node built-ins and a key that is safe to ship in page source. That package, @evolve/logs/browser, is in development. The edge half — Workers, Vercel Edge, Deno Deploy — is shipped today as @evolve/logs/edge.

What works today

Server-rendered pages, route handlers, server actions and API routes run on Node. Initialise the SDK from instrumentation.ts and report request errors from onRequestError:

text
// instrumentation.ts (Next.js)
import { init, log } from '@evolve/logs';

export function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    init({
      key: process.env.EVOLVE_LOGS_KEY,
      url: process.env.EVOLVE_LOGS_URL,
      service: 'web',
    });
  }
}

export function onRequestError(err: unknown, request: { method: string }, ctx: { routePath: string }) {
  log.exception(err, { route: ctx.routePath, method: request.method });
}

Every server-side failure then reaches Observer with a stack and joins the trace of the API calls the page made. What a user’s browser throws (a render crash, a rejected fetch in the client) does not reach Observer yet.

Do not import @evolve/logs into a client bundle, and never put a server ingest key (evk_…) in page source. A server key writes to the project from any origin; anyone who reads it can flood or pollute your logs.

What the browser SDK adds

The browser entry ships inside the same npm package as a second entry point, so a team with a Node backend and a React front end installs one dependency. It is written for the platform rather than for Node: fetch with keepalive for the last batch, navigator.sendBeacon on pagehide, CompressionStream where the browser has it, crypto.getRandomValues for ids, and an explicit context object instead of ambient async context.

typescript
import { init, captureError, log } from '@evolve/logs/browser';

init({
  key: 'evk_pub_...',          // a public key, bound to one project and your origins
  url: 'https://api.e-volv.io/api/public/v1/logs',
  service: 'web',
  release: __GIT_SHA__,
});

// React error boundary, Vue errorHandler, window.onerror — one call
captureError(error, { route: location.pathname });
log.warn('checkout slow', { ms: 4200 });

Public keys. A browser key has the prefix evk_pub_, is bound to one project, can only write logs, is restricted by an origin allowlist that the ingest enforces against the Origin header, and is rate-limited per key. A server key is refused from a browser origin, and a public key is refused everywhere except the ingest routes.

Joined traces. The SDK reads the traceparent from the response of the request that failed, so a front-end error carries the trace id of the server hop that caused it and appears on the same failure graph.

No replay, no vitals. Errors and logs with a trace id. Sampling is an init() option; source maps for minified frames are a later step.

Edge runtimes

Cloudflare Workers, Vercel Edge Functions and Deno Deploy freeze the isolate the moment the response is sent — a fire-and-forget fetch started after the response may never run. The edge client exists for exactly this: it batches like every other SDK and hands its flush to the runtime’s waitUntil, which keeps the isolate alive until the batch leaves. Trace context is an explicit object (edge runtimes have no ambient async context), so a child span takes its parent’s context as an argument.

Cloudflare Workers:

typescript
import { createEdgeClient } from '@evolve/logs/edge';

const logs = createEdgeClient({
  key: 'evk_pub_...',
  url: 'https://api.e-volv.io/api/public/v1/logs',
  service: 'edge-gateway',
});

export default {
  async fetch(request, env, ctx) {
    try {
      return await handle(request);
    } catch (err) {
      logs.exception(err, { route: new URL(request.url).pathname });
      throw err;
    } finally {
      logs.flushOnEnd(ctx); // ctx.waitUntil keeps the batch past the response
    }
  },
};

Vercel Edge (pass waitUntil once at construction):

typescript
import { createEdgeClient } from '@evolve/logs/edge';

export async function GET(request: Request) {
  const logs = createEdgeClient({
    key: 'evk_pub_...',
    url: 'https://api.e-volv.io/api/public/v1/logs',
    service: 'edge-api',
    waitUntil, // from the request context
  });
  try {
    const span = logs.span('http.request');
    const res = await handle(request);
    span.end();
    return res;
  } finally {
    logs.flushOnEnd();
  }
}

Batching, redaction and retry numbers are the shared ones (200 events / 2 s / 512 KB; 429 honours Retry-After, 413 halves, oldest-first drops counted on client.dropped). Edge payloads go out identity-encoded — zlib does not exist in an isolate and the ingest accepts both encodings.

Until then

Keep front-end error reporting where it is, and forward the server side through the Node SDK. When the browser entry ships it is the same package, the same wire format and the same project; nothing on the server changes.

Back to SDK overview.