Quickstart

@marsadco/sdk is one package for Next.js and React Native / Expo. It captures unhandled errors, manual exceptions and network requests — plus sessions, Web Vitals and cookieless page views, with performance traces one option away — and ships them to your Marsad project. The runtime is auto-detected; you just call init().

Install

Add the single package. It has zero runtime dependencies; React is an optional peer (only needed for the ErrorBoundary).

Next.js
bash
npm install @marsadco/sdk
Expo / React Native
bash
npx expo install @marsadco/sdk

Get your key

Grab your project’s ingest key from Settings → API keys (or the Get started page). It looks like bk_live_…. Set it as an env var — MARSAD_KEY (server), NEXT_PUBLIC_MARSAD_KEY (Next.js browser) or EXPO_PUBLIC_MARSAD_KEY (Expo) — and init() picks it up automatically, so you don’t have to pass key at all. The endpoint defaults to the hosted ingest URL, so you normally don’t set it either.

Initialize — Next.js

The @marsadco/sdk/nextjs helper is the simplest path — it reads MARSAD_KEY / NEXT_PUBLIC_MARSAD_KEY for you. Re-export register from instrumentation.ts for the server, and call initBrowser() from a small client component mounted once in your root layout.

instrumentation.ts (server)
ts
export { register, onRequestError } from "@marsadco/sdk/nextjs";
app/marsad-init.tsx (client)
tsx
"use client";
import { useEffect } from "react";
import { initBrowser } from "@marsadco/sdk/nextjs";

export function MarsadInit() {
  useEffect(() => initBrowser(), []);
  return null;
}

Render <MarsadInit /> once inside app/layout.tsx. The ErrorBoundary and FeedbackWidget come from @marsadco/sdk/react — the Next.js helper is React-free on purpose. Prefer to wire it by hand? Call init() from @marsadco/sdk directly — it still reads the key from the env when you don’t pass one.

Re-exporting onRequestError captures every server-side error Next catches — server actions, route handlers, RSC and SSR renders — that never reaches the global handler. For a single server action you can also wrap it: withMarsad(action) (or wrapRouteHandler(handler)) captures, flushes, and re-throws. Client navigation, click and console breadcrumbs are captured automatically — no wiring needed.

Initialize — Expo / React Native

Use @marsadco/sdk/expo — it reads EXPO_PUBLIC_MARSAD_KEY and re-exports the React Native ErrorBoundary. Call init() at your app entry, then wrap your tree in the boundary to catch render-time errors with the component stack.

App.tsx
tsx
import { init, ErrorBoundary } from "@marsadco/sdk/expo";

init(); // key from EXPO_PUBLIC_MARSAD_KEY

export default function App() {
  return (
    <ErrorBoundary fallback={<Fallback />}>
      <RootNavigator />
    </ErrorBoundary>
  );
}

React Native has no browser history to patch, so two things take your current route: navigation breadcrumbs, and screen views — the mobile equivalent of the page views captured automatically on the web. With Expo Router:

app/_layout.tsx (Expo Router)
tsx
import { usePathname } from "expo-router";
import { useNavigationBreadcrumbs, useScreenViews } from "@marsadco/sdk/expo";

export default function Layout() {
  const pathname = usePathname();
  useScreenViews(pathname);           // screen views → Analytics page
  useNavigationBreadcrumbs(pathname); // trail attached to the next error
  // …
}

With React Navigation, pass useRoute().name instead. Screens are attributed to ios / androidautomatically and appear beside your web traffic. Pass storage: AsyncStorage to init() so queued events survive app restarts.

Verify it works

Send a test event. Within a few seconds it should appear, grouped, on your Issues page.

import { captureException } from "@marsadco/sdk";

const code = captureException(new Error("Marsad test event"));
console.log(code); // → "MAR-K7P2QX9M" — searchable in ⌘K

captureException and captureMessage return a short reference code (MAR-…) you can surface to users; paste it into the dashboard’s ⌘K palette to jump straight to the issue.

See the event? You’re live. Unhandled errors, rejections and fetch / XHR calls are now captured automatically — each enriched with runtime context and breadcrumbs, no manual logging required.

Session tracking is on by default too: the SDK pings a session on each app start and marks it crashed on an unhandled error, powering the crash-free rate on your Overview. Web Vitals (LCP/CLS/INP/FCP/TTFB) are captured automatically on the web and shown as a p75 card there. On the web you also get cookieless analytics out of the box — a page view on load and on every route change, with referrers, devices, countries and UTM campaigns on the Analytics page, and no consent banner to add. The event queue is persisted and retried with backoff, so events survive reloads, offline periods and crashes. Opt into tracesSampleRate and the SDK also records performance traces — pageloads, navigations and requests — viewable as waterfalls on the Performance page.

From there, attach setTag(key, value) / setTags({…}) to make events filterable by plan, region or tenant, call captureFeedback({ message, eventCode }) with the reference code to tie a user’s report to its issue, and use track(name, props) to count product events next to your traffic. See the SDK reference for the full API.

Upload source maps

So minified frames resolve to your real files and line numbers, upload your source maps after each build with the bundled CLI. Run it in CI or a postbuild step; stacks are symbolicated server-side the first time an issue is viewed.

postbuild
bash
# authenticated by your ingest key (--key or $MARSAD_KEY)
# inject stamps each chunk with a debug id; upload ships the maps
npx marsad inject ./dist && npx marsad upload-sourcemaps ./dist

inject writes a debugId into every chunk and its map, so a stack resolves to the exact build with no release to keep in sync. Prefer matching by release instead? Skip inject and pass --release (or $MARSAD_RELEASE) — the SDK auto-detects it in CI from env vars like VERCEL_GIT_COMMIT_SHA or GITHUB_SHA, so pass the same value to the CLI.

Starter apps

Prefer to start from something that already works? Two starters live in the repo under examples/: a Next.js App Router app (server + browser init, ErrorBoundary, FeedbackWidget, custom events, and source-map upload wired into postbuild) and an Expo Router app (init with AsyncStorage persistence, screen views, navigation breadcrumbs, feedback). Each has buttons that send a handled error, an unhandled error, a custom event and a metric, so you can confirm the whole pipeline in under a minute.

Next steps