npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@browsonic/remix

v1.2.19

Published

Remix adapter for @browsonic/sdk — route ErrorBoundary, action wrapper, client-entry capture. Re-exports @browsonic/react. Apache-2.0.

Downloads

519

Readme

@browsonic/remix

Remix adapter for @browsonic/sdk — a route ErrorBoundary component, action / loader wrappers that stamp remix.handler, an entry.client.tsx config helper, a route-hierarchy navigation breadcrumb hook, plus part of the @browsonic/react surface re-exported.

Status: released. package.json is 1.2.15, and npm's latest is the same 1.2.15 (published 2026-07-08; registry checked 2026-07-27). The 0.1 / 0.2 / 0.3 labels in the source doc comments are pre-1.0 development milestones, not releases — all three landed together in 1.0.0 (2026-05-07) and this package has never had a 0.x release.

Exports: BrowsonicRouteErrorBoundary, captureRouteError, withBrowsonicRemixAction, withBrowsonicRemixLoader, bootstrapBrowsonic, useRemixNavigationBreadcrumbs, resolveSdk, plus the re-exported React names listed below. Nothing here imports @remix-run/* at runtime, and @remix-run/* appears in no dependency block at all — the Remix values the hook consumes are described by structural interfaces (NavigationLike, MatchLike) declared in this package — so the adapter is not tied to a particular Remix build mode.

Why this adapter

Remix's error model is route-scoped: each route module can export an ErrorBoundary component that the framework renders when the route's loader / action / component throws. We ship a component you hand that error to, which captures it on mount.

Remix delivers the route error through useRouteError(), not through props, so export { BrowsonicRouteErrorBoundary as ErrorBoundary } on its own captures nothing — call useRouteError() yourself and pass the value in, as the quickstart below does. Without an error prop the component only renders its fallback.

@browsonic/react is a peer dependency and part of its surface is re-exported here, so Remix consumers import from one package instead of two — the same subset @browsonic/nextjs re-exports. It is not a one-install story: you install three packages.

Install

npm install @browsonic/sdk @browsonic/react @browsonic/remix

@browsonic/sdk (>=3.12.0), @browsonic/react (^1.2.13) and react (^18 or ^19) are peer dependencies. This package declares no runtime dependencies of its own.

Quickstart — Route ErrorBoundary

// app/routes/some-route.tsx
import { useRouteError } from '@remix-run/react';
import { BrowsonicRouteErrorBoundary } from '@browsonic/remix';

export function ErrorBoundary() {
  const error = useRouteError();
  // Pass the error through; the boundary captures + renders fallback
  return <BrowsonicRouteErrorBoundary error={error} />;
}

export default function Page() {
  return <div>...</div>;
}

The captured event carries the metadata key remixRouteError plus a remix context bucket with handler: 'routeError'. Both are scoped to that one capture (sdk.withScope), so they do not stick to later events.

Or use the imperative companion when you want a custom fallback UI (it emits the remixRouteError metadata key only — no remix context bucket):

import { useRouteError } from '@remix-run/react';
import { captureRouteError } from '@browsonic/remix';

export function ErrorBoundary() {
  const error = useRouteError();
  captureRouteError(error);
  return <MyCustomErrorScreen error={error} />;
}

Quickstart — entry.client.tsx bootstrap

bootstrapBrowsonic writes window.Browsonic.config. It does not load or start the SDK: you also call getBrowsonic() from @browsonic/sdk, and creating that singleton is what makes the SDK pick the config up and initialise.

// app/entry.client.tsx
import { RemixBrowser } from '@remix-run/react';
import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';
import { getBrowsonic } from '@browsonic/sdk';
import { bootstrapBrowsonic } from '@browsonic/remix';

bootstrapBrowsonic({
  apiEndpoint: 'https://your-ingest-endpoint.test/v1/events',
  appKey: 'your-app-key',
  // Sent as the X-API-KEY header. POST /v1/events is role-gated, so a batch
  // with no key is rejected with 403 before it reaches the handler.
  apiKey: 'your-publishable-ingest-key',
  environment: 'production',
});

// Creates the singleton, which auto-initialises from the config written above.
// The SDK logs a "[Browsonic] Auto-initialising from window.Browsonic.config"
// warning when it does — that is deliberate, so an injected config is auditable.
getBrowsonic();

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <RemixBrowser />
    </StrictMode>,
  );
});

bootstrapBrowsonic reads any existing window.Browsonic.config first (so entry.server.tsx can serialise per-request fields through an injected <script>), merges every defined option on top — undefined is skipped, so apiKey: window.ENV?.BROWSONIC_KEY never clobbers a server-injected value — and returns the SDK singleton if one already exists, otherwise null. SSR-safe: on Node it returns null without touching globals.

One trap: the option type accepts release, but release is not a field the SDK reads — resolveConfig in @browsonic/sdk picks a fixed set of keys and config.release is referenced nowhere in the SDK. Use clientVersion for the build identifier.

Quickstart — Action / loader wrappers

// app/routes/checkout.tsx
import { withBrowsonicRemixAction, withBrowsonicRemixLoader } from '@browsonic/remix';

export const loader = withBrowsonicRemixLoader(async ({ request }) => {
  // ... data fetch that may throw
});

export const action = withBrowsonicRemixAction(async ({ request }) => {
  const data = await request.formData();
  if (!data.get('email')) throw new Error('email required');
  return { ok: true };
});

Both wrappers stamp the captured event twice. remix.handler: 'action' | 'loader' goes on via sdk.setTag(), which the SDK implements as an alias of addMetadata — so it rides in the event's metadata and is reachable from the dashboard's metadata key=value filter. The same value is mirrored into the remix context bucket, which the dashboard renders as its "Remix context" card. The 0.1-era metadata keys remixAction / remixLoader are still emitted alongside, for back-compat.

Where this actually fires. In a standard Remix app loader and action run on the server, where there is no browser SDK: resolveSdk() returns null, nothing is reported, and the wrapper is a pass-through that re-throws the original error so Remix's response pipeline is preserved. Capture happens only when the wrapped function runs in the browser — i.e. when you wrap clientLoader / clientAction.

Quickstart — Navigation breadcrumbs with route hierarchy

useRemixNavigationBreadcrumbs(useNavigation(), useMatches()) emits a category: 'navigation' breadcrumb each time the Remix navigation state transitions from non-idle'idle' (submitting → idle counts, so form-action navigations are included). Repeated idle renders — revalidations, fetcher submits — do not fire it. The first completed transition is suppressed by default; pass { skipInitial: false } to keep it. { category } overrides the breadcrumb category, { sdk } supplies an explicit instance.

// app/root.tsx
import { Outlet, useNavigation, useMatches } from '@remix-run/react';
import { useRemixNavigationBreadcrumbs } from '@browsonic/remix';

export default function App() {
  useRemixNavigationBreadcrumbs(useNavigation(), useMatches());
  return <Outlet />;
}

Breadcrumb data (the breadcrumb message is `${from} → ${to}`):

{
  from: '/dashboard/users/42',   // see the defect note — currently equals `to`
  to: '/dashboard/users/42',
  routeId: 'routes/_app.dashboard.users.$userId',           // leaf
  routeChain: 'routes/_app › routes/_app.dashboard › routes/_app.dashboard.users › routes/_app.dashboard.users.$userId',
}

routeId and routeChain are omitted when matches is empty.

Known defect (2026-07-27, not fixed): from is not the origin path. While a navigation is in flight the hook records navigation.location.pathname — the destination — as the "previous" path, so whenever the in-flight navigation carries a location (the normal case) from and to come out equal on the render that completes the transition. The package's own test pins the current behaviour: it asserts the message '/dashboard → /dashboard'. to, routeId and routeChain are unaffected.

Cross-shell URLs that look identical (e.g. /users/42 inside _app vs a public route) become distinguishable in incident triage through routeId / routeChain.

Quickstart — React surface

Re-exported from @browsonic/react, so you don't need a separate import: BrowsonicErrorBoundary (with its BrowsonicErrorBoundaryProps and BrowsonicErrorBoundaryFallback types), useBrowsonic, useUser, useCaptureError, withBrowsonic.

Not re-exported, even though @browsonic/react exports them: the Atlas helpers routeTemplateFromMatches / useTrackPageView / RouteMatchLike, and the WithBrowsonicInjectedProps type. Import @browsonic/react directly for those.

import { Outlet } from '@remix-run/react';
import { BrowsonicErrorBoundary, useUser } from '@browsonic/remix';

export default function Layout() {
  useUser({ id: 'u1' });
  return (
    <BrowsonicErrorBoundary fallback={(err) => <div>{err.message}</div>}>
      <Outlet />
    </BrowsonicErrorBoundary>
  );
}

Defensive contract

  • The host app must never crash because reporting failed.
  • All SDK calls in try { … } catch {}.
  • The route boundary still renders fallback when the SDK is unreachable, and when the reporter itself throws.
  • The action / loader wrappers still re-throw the original error even when the reporter throws, and pass the handler's resolved value through unchanged. Their declared return type is Promise<TReturn>, so wrapping a synchronous handler makes it promise-returning.

What this package does NOT do

  • Load or initialise the SDK for you. bootstrapBrowsonic only writes window.Browsonic.config; the getBrowsonic() call in entry.client.tsx is yours to add. There is no script auto-injection.
  • Server-runtime capture in Node. The SDK is a browser library; action / loader errors that occur in pure Node have no window to write to. The wrapper still re-throws so Remix returns the expected status. Wire your own server logging if needed.
  • Edge runtime instrumentation. This is queued behind the SDK core gaining a multi-runtime build target, which is an intentional project non-goal — so it does not come due on its own. Reopens only if the SDK core adds one. (Recorded in ROADMAP.md.)
  • <RemoteCatch> / pre-Remix-v2 CatchBoundary back-port. Closed 2026-07-27, not parked. Remix v2 replaced CatchBoundary with the unified ErrorBoundary + useRouteError, which is the path this adapter ships. If you are still on pre-v2 Remix, upgrading is the supported route.

License

Apache-2.0. See the repo root LICENSE and the package NOTICE.