@browsonic/vue

Vue 3 adapter for @browsonic/sdk — error boundary, composables, app plugin with errorHandler chaining. Apache-2.0.
5,439
Install
npm install @browsonic/vue
Latest Version:1.6.3
Vue:3
License:Apache-2.0
Last Updated:Sep 6, 2026
Links: npm
Maintainer: leguide

@browsonic/vue

Vue 3 adapter for @browsonic/sdk — error boundary component, composables, an app.use() plugin that chains into app.config.errorHandler, Vue Router instrumentation (breadcrumbs + Atlas route-template page views), and a Pinia integration.

Status: 1.5.x surface — check npm or this package's package.json for the current version. Boundary, four composables (useBrowsonic / useUser / useCaptureError / useBreadcrumb), plugin install, Vue Router 4 instrumentation — navigation breadcrumbs (with optional beforeEach intent phase) plus Atlas route-template page views (routeTemplateFromLocation) and the screen-name channel (routeNameFromLocation), on by default under the SDK ≥ 3.14 atlas: true umbrella — and a Pinia plugin that stamps store action errors with context. Server-side rendering capture is intentionally out of scope (browser-runtime adapter only).

Why this adapter

Vue's reconciler captures render-time and lifecycle exceptions in descendant components and routes them through errorCaptured / app.config.errorHandler. The plain @browsonic/sdk install therefore never hears about them — only the global error / unhandledrejection events. This package closes that gap.

Install

npm install @browsonic/sdk @browsonic/vue

@browsonic/sdk is a peer dependency. vue (3.3+) is a peer dependency.

Quickstart

import { createApp } from 'vue';
import { getBrowsonic } from '@browsonic/sdk';
import { browsonicPlugin, BrowsonicErrorBoundary } from '@browsonic/vue';
import App from './App.vue';

const sdk = getBrowsonic();
sdk.init({
  apiEndpoint: 'https://your-ingest-endpoint.test/v1/events',
  appKey: 'web',
  apiKey: 'pk_live_...', // publishable key — safe to ship in the browser
});

const app = createApp(App);
app.use(browsonicPlugin, { sdk });
app.component('BrowsonicErrorBoundary', BrowsonicErrorBoundary);
app.mount('#app');

Wrap the parts of your tree you want to isolate:

<BrowsonicErrorBoundary :fallback="ErrorScreen">
  <Routes />
</BrowsonicErrorBoundary>

API

browsonicPlugin

app.use(browsonicPlugin, {
  sdk, // optional; falls back to window.Browsonic.getBrowsonic()
  chainErrorHandler: true, // optional; default true — chains into app.config.errorHandler
});

Wires provide(browsonicInjectionKey, sdk) so composables can resolve via inject, and chains into app.config.errorHandler so errors that aren't caught by a boundary still reach the SDK. The previously-installed handler is preserved and called after the report.

<BrowsonicErrorBoundary>

<BrowsonicErrorBoundary :fallback="(ctx) => h('div', 'Crashed: ' + ctx.error.message)">
  <Inner />
</BrowsonicErrorBoundary>

Props:

Prop Type Required
fallback Component | ((ctx: { error, reset }) => unknown) yes
sdk Browsonic no
onError (error: Error, info: string) => void no

When a child throws during render or in a lifecycle hook, the boundary forwards the error to the SDK (truncating any component stack to 1024 characters as componentStack metadata) and then renders the fallback. Returning false from errorCaptured stops further propagation.

useBrowsonic() / useUser() / useCaptureError() / useBreadcrumb()

import { useBrowsonic, useUser, useCaptureError, useBreadcrumb } from '@browsonic/vue';

const sdk = useBrowsonic();
useUser({ id: 'u1', email: 'a@b.test' });

const captureError = useCaptureError();
const addBreadcrumb = useBreadcrumb();
function onClick() {
  addBreadcrumb({ category: 'ui.click', message: 'buy button' });
  try {
    risky();
  } catch (err) {
    captureError(err as Error);
  }
}

useUser accepts a plain value or a Ref (re-applies on change, immediate). All four composables are no-ops when the SDK is unreachable.

installRouterInstrumentation(router, options?)

Subscribes to a Vue Router 4 afterEach guard and emits a category: 'navigation' breadcrumb on every successful route change. Pass { includeIntent: true } to also subscribe to beforeEach and emit a phase: 'intent' breadcrumb before the navigation begins (the existing afterEach breadcrumb gets phase: 'completed'). Returns the unsubscribe handle from Vue Router for HMR-friendly teardown.

import { createRouter, createWebHistory } from 'vue-router';
import { installRouterInstrumentation } from '@browsonic/vue';

const router = createRouter({ history: createWebHistory(), routes: [...] });
const off = installRouterInstrumentation(router, { includeIntent: true });
// optional: off() during HMR teardown

Structural RouterLike shape — no vue-router runtime dep. Intent breadcrumbs are the only record of an attempted route when an error fires mid-navigation, so includeIntent: true is recommended for production observability.

Atlas route-template page views (trackPageViews). On every completed navigation the instrumentation can also drive the SDK's page-view channel with trackPageView(template, { name }) — the parameterized route template (/users/:id) that Atlas's URL normalization can never recover for alphabetic slugs, plus the host-authored screen name (see below).

  • Default (SDK ≥ 3.14): follows the core's atlas: true umbrella, feature-detected per event via Browsonic.isAtlasEnabled(). When the host initialized with atlas: true, template tracking turns on here with no second flag, and the core hands organic page views over on the first templated view — no double counting, no manualPageViews pairing. On older cores the default reads as false.
  • Explicit trackPageViews: true keeps the legacy contract on older cores — pair it with manualPageViews: true in the SDK init there, otherwise the organic history-driven page view double counts every navigation. Explicit false keeps the channel off even under atlas: true.
  • The page-view channel deliberately ignores skipInitial — that flag de-dupes breadcrumb noise, and the initial navigation is exactly the page view that maps the entry screen.

routeTemplateFromLocation(to) / routeNameFromLocation(to)

The extraction helpers the instrumentation uses, exported for hosts that drive trackPageView themselves:

  • routeTemplateFromLocation(to) — the parameterized route template from a resolved location: the deepest to.matched record's path (Vue Router stores each record's full absolute template, so no joining is needed). Returns '' when nothing matched — send undefined then, so Atlas falls back to URL normalization.
  • routeNameFromLocation(to) — the host-authored screen name: to.meta.browsonicName (explicit) or to.meta.title (the common Vue Router convention), trimmed. Returns '' when the route declares neither. Feeds the SDK's screen-name channel, so a route can name its screen from code (dashboard naming rules still override it server-side).
const routes = [
  { path: '/users/:id', component: UserProfile, meta: { browsonicName: 'User Profile' } },
];

installPiniaIntegration(pinia, options?)

Pinia plugin that hooks every store's $onAction.onError so unhandled action errors stamp the SDK scope with setContext('pinia', { storeId, action, args, errorMessage, state? }) before bubbling. The Vue boundary or window error handler then captures an event that already knows which store + action caused the failure.

import { createPinia } from 'pinia';
import { installPiniaIntegration } from '@browsonic/vue';

const pinia = createPinia();
installPiniaIntegration(pinia, {
  // Opt-in: include store state snapshot. Off by default — Pinia stores
  // commonly hold auth tokens / PII; audit per app before enabling.
  captureState: false,
  // Skip specific stores by `$id` (auth / wallet stores benefit).
  ignoreStores: ['auth'],
});
app.use(pinia);

Structural PiniaLike / PiniaStoreLike — no pinia runtime dep. maxLength (default 4096) caps serialised args / state to keep event payload size bounded.

Defensive contract

Every public surface follows the same rule:

  • The host app must never crash because reporting failed.
  • SDK calls are wrapped in try { ... } catch {}. If the boundary, plugin, or composable can't reach the SDK, it stays silent.
  • Composables and the boundary work without the plugin — they fall back to window.Browsonic.getBrowsonic().

What this package does NOT do

  • Server-side rendering capture. Out of scope. The SDK is a browser library; the adapter is a browser library.
  • Vue 2 / Options-API-only consumers. Vue 3.3+ Composition API is the contract. Vue 2 has reached end-of-life; we are not back-porting. (Note: Options API components inside a Vue 3 app are supported — the boundary catches errors from both authoring styles. The parity test suite pins this contract.)
  • Vuex state snapshot on error. Pinia is the supported store integration via installPiniaIntegration. Vuex consumers can mirror the same shape with sdk.setContext('vuex', ...) from a custom plugin, but we don't ship one.

License

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

Related Packages

ets2-dashboard-skin

Euro Truck Simulator 2 dashboard

3,223 67
@truewatchtech/browser-rum-nuxt

TrueWatch Browser SDK RUM Nuxt integration

2,449 0
@cloudcare/browser-rum-vue

Guance Browser SDK RUM Vue integration

2,405 54
@cloudcare/browser-rum-nuxt

Guance Browser SDK RUM Nuxt integration

2,399 54
@truewatchtech/browser-rum-vue

TrueWatch Browser SDK RUM Vue integration

2,395 0

Version History

Version Released Vue License
1.6.3 ^3.3.0 Apache-2.0
1.6.2 ^3.3.0 Apache-2.0
1.6.1 ^3.3.0 Apache-2.0
1.6.0 ^3.3.0 Apache-2.0
1.5.8 ^3.3.0 Apache-2.0