mirror of
https://github.com/getsentry/sentry-for-ai.git
synced 2026-09-14 14:28:22 +08:00
64dfeaa1f1
These per-SDK skills, plus a handful of setup/workflow skills, are superseded by the sentry-instrument skill and the references/sdks/ trees. Move them under skills-legacy/ so they are excluded from the plugin build and skipped by the drift automation, while skills.sentry.dev can still serve them as a fallback for existing URL consumers. Moves 26 skills: the 19 per-SDK skills plus sentry-sdk-skill-creator, sentry-fix-issues, the three sentry-span-streaming-* skills, sentry-instrument-logging, and sentry-instrumentation-guide. Regenerates SKILL_TREE.md and repaths the moved entries in CODEOWNERS. sentry-sdk is intentionally left in place; its removal is handled in a separate PR.
1225 lines
40 KiB
Markdown
1225 lines
40 KiB
Markdown
# React-Specific Features — Sentry React SDK
|
||
|
||
> Minimum SDK: `@sentry/react` v8.0.0+
|
||
|
||
This is the catch-all deep dive for React-specific Sentry features: Redux integration, component tracking, source maps, and the full integrations catalog.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Redux Integration](#1-redux-integration)
|
||
2. [Component Tracking & Performance](#2-component-tracking--performance)
|
||
3. [Source Maps](#3-source-maps)
|
||
4. [Default Integrations](#4-default-integrations)
|
||
5. [Optional Integrations](#5-optional-integrations)
|
||
6. [Build Tool Detection & Environment Variables](#6-build-tool-detection--environment-variables)
|
||
|
||
---
|
||
|
||
## 1. Redux Integration
|
||
|
||
`Sentry.createReduxEnhancer()` hooks into your Redux store to automatically:
|
||
|
||
- Capture **Redux actions as breadcrumbs** on every Sentry error event
|
||
- Attach the **Redux state as a `redux_state.json` file** to error events
|
||
- Keep **Sentry scope tags in sync** with your Redux state
|
||
|
||
### Setup — Redux Toolkit (`configureStore`)
|
||
|
||
```typescript
|
||
import * as Sentry from "@sentry/react";
|
||
import { configureStore } from "@reduxjs/toolkit";
|
||
import rootReducer from "./reducers";
|
||
|
||
const sentryReduxEnhancer = Sentry.createReduxEnhancer({
|
||
// options — see below
|
||
});
|
||
|
||
const store = configureStore({
|
||
reducer: rootReducer,
|
||
enhancers: (getDefaultEnhancers) => {
|
||
return getDefaultEnhancers().concat(sentryReduxEnhancer);
|
||
},
|
||
});
|
||
|
||
export type RootState = ReturnType<typeof store.getState>;
|
||
export type AppDispatch = typeof store.dispatch;
|
||
export default store;
|
||
```
|
||
|
||
> ⚠️ **Critical:** `sentryReduxEnhancer` is a **store enhancer**, not Redux middleware. Do **NOT** pass it inside `applyMiddleware()`.
|
||
|
||
### Setup — Legacy `createStore` with Middleware
|
||
|
||
```javascript
|
||
import { createStore, applyMiddleware, compose } from "redux";
|
||
import thunk from "redux-thunk";
|
||
import * as Sentry from "@sentry/react";
|
||
|
||
const sentryReduxEnhancer = Sentry.createReduxEnhancer();
|
||
|
||
const store = createStore(
|
||
rootReducer,
|
||
compose(applyMiddleware(thunk), sentryReduxEnhancer),
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### Options: `actionTransformer` — Filter/Scrub Actions Before Breadcrumbs
|
||
|
||
Called for every dispatched action. Return the action to include it as a breadcrumb, a modified copy to scrub sensitive fields, or `null` to drop it entirely.
|
||
|
||
```typescript
|
||
const sentryReduxEnhancer = Sentry.createReduxEnhancer({
|
||
actionTransformer: (action) => {
|
||
// Drop high-volume or sensitive actions entirely
|
||
if (action.type === "WEBSOCKET_PING") return null;
|
||
if (action.type === "FETCH_SECRETS") return null;
|
||
|
||
// Scrub sensitive fields from certain actions
|
||
if (action.type === "USER_LOGIN") {
|
||
return {
|
||
...action,
|
||
password: "[REDACTED]",
|
||
token: "[REDACTED]",
|
||
};
|
||
}
|
||
|
||
if (action.type === "UPDATE_PAYMENT") {
|
||
return {
|
||
...action,
|
||
payload: {
|
||
...action.payload,
|
||
cardNumber: null,
|
||
cvv: null,
|
||
},
|
||
};
|
||
}
|
||
|
||
// Include all other actions as-is
|
||
return action;
|
||
},
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### Options: `stateTransformer` — Filter/Scrub State Snapshots
|
||
|
||
Called on every state update. Return the state to attach it to error events, a modified copy with sensitive fields redacted, or `null` to exclude state entirely.
|
||
|
||
```typescript
|
||
const sentryReduxEnhancer = Sentry.createReduxEnhancer({
|
||
stateTransformer: (state: RootState) => {
|
||
// Return null to send NO state with errors (reduces context but protects PII)
|
||
// if (state.topSecret.active) return null;
|
||
|
||
// Return a scrubbed copy
|
||
return {
|
||
...state,
|
||
auth: {
|
||
...state.auth,
|
||
token: null, // remove auth token
|
||
refreshToken: null,
|
||
password: null,
|
||
},
|
||
user: {
|
||
...state.user,
|
||
ssn: "[REDACTED]",
|
||
creditCard: null,
|
||
dateOfBirth: null,
|
||
},
|
||
// Remove entire subtrees you don't need
|
||
cache: null,
|
||
rawApiResponses: null,
|
||
};
|
||
},
|
||
});
|
||
```
|
||
|
||
> ⚠️ **Warning:** If `stateTransformer` returns `null`, error events will lack Redux state context. Debugging large state-dependent bugs becomes much harder. Prefer returning a filtered copy over returning `null`.
|
||
|
||
---
|
||
|
||
### Options: `configureScopeWithState` — Derive Sentry Tags from Redux State
|
||
|
||
Called after every state update. Use it to keep Sentry scope tags/context in sync with your application's Redux state — these tags then appear on every error captured after the update.
|
||
|
||
```typescript
|
||
const sentryReduxEnhancer = Sentry.createReduxEnhancer({
|
||
configureScopeWithState: (scope: Sentry.Scope, state: RootState) => {
|
||
// Tag events with current user plan (great for filtering errors by customer tier)
|
||
scope.setTag("user.plan", state.user.plan);
|
||
scope.setTag("user.id", state.user.id);
|
||
|
||
// Tag with feature flag state
|
||
scope.setTag("feature.newCheckout", String(state.features.newCheckout));
|
||
scope.setTag("feature.darkMode", String(state.settings.darkMode));
|
||
|
||
// Tag with routing/navigation state
|
||
scope.setTag("app.currentFlow", state.navigation.currentFlow);
|
||
|
||
// Conditional tags based on state shape
|
||
if (state.settings.useImperialUnits) {
|
||
scope.setTag("user.usesImperialUnits", "true");
|
||
}
|
||
|
||
// Set structured context (not searchable but visible in issue detail)
|
||
scope.setContext("cart", {
|
||
itemCount: state.cart.items.length,
|
||
total: state.cart.total,
|
||
coupon: state.cart.couponCode ?? null,
|
||
});
|
||
},
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### Options: `attachReduxState` — Control State File Attachment
|
||
|
||
Controls whether the Redux state (post-`stateTransformer`) is attached as a `redux_state.json` file on error events.
|
||
|
||
- **Type:** `boolean`
|
||
- **Default:** `true`
|
||
- **Min SDK:** `7.69.0`
|
||
|
||
```typescript
|
||
const sentryReduxEnhancer = Sentry.createReduxEnhancer({
|
||
attachReduxState: false, // Don't attach state file — reduces payload size
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### Options: `normalizeDepth` — Control State Serialization Depth
|
||
|
||
Set in `Sentry.init()`, not in `createReduxEnhancer()`. Increases the depth at which Redux state trees are serialized. The default of `3` is too shallow for most Redux state shapes.
|
||
|
||
```typescript
|
||
Sentry.init({
|
||
dsn: "___PUBLIC_DSN___",
|
||
normalizeDepth: 10, // Default is 3 — increase for deeply nested Redux state
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### All Options Summary
|
||
|
||
| Option | Type | Default | Location | Description |
|
||
|--------|------|---------|----------|-------------|
|
||
| `actionTransformer` | `(action: Action) => Action \| null` | — | `createReduxEnhancer()` | Modify or drop action breadcrumbs |
|
||
| `stateTransformer` | `(state: State) => State \| null` | — | `createReduxEnhancer()` | Modify or drop state attached to events |
|
||
| `configureScopeWithState` | `(scope: Scope, state: State) => void` | — | `createReduxEnhancer()` | Sync Sentry scope tags/context with Redux state |
|
||
| `attachReduxState` | `boolean` | `true` | `createReduxEnhancer()` | Attach state as `redux_state.json` file on errors |
|
||
| `normalizeDepth` | `number` | `3` | `Sentry.init()` | Max depth when serializing nested state |
|
||
|
||
---
|
||
|
||
### Complete Working Example with `@reduxjs/toolkit`
|
||
|
||
```typescript
|
||
// store/sentry.ts
|
||
import * as Sentry from "@sentry/react";
|
||
import type { RootState } from "./types";
|
||
|
||
export const sentryReduxEnhancer = Sentry.createReduxEnhancer({
|
||
actionTransformer: (action) => {
|
||
// Drop noisy/sensitive action types
|
||
const DROP_TYPES = new Set([
|
||
"SET_AUTH_TOKEN",
|
||
"REFRESH_TOKEN",
|
||
"WEBSOCKET_HEARTBEAT",
|
||
"UPDATE_CURSOR_POSITION",
|
||
]);
|
||
if (DROP_TYPES.has(action.type)) return null;
|
||
|
||
// Scrub passwords from login actions
|
||
if (action.type === "auth/login/pending") {
|
||
return { ...action, meta: { ...action.meta, arg: { email: action.meta?.arg?.email, password: "[REDACTED]" } } };
|
||
}
|
||
|
||
return action;
|
||
},
|
||
|
||
stateTransformer: (state: RootState) => ({
|
||
...state,
|
||
auth: { isAuthenticated: state.auth.isAuthenticated, userId: state.auth.userId },
|
||
// Strip large or sensitive subtrees
|
||
rawData: null,
|
||
}),
|
||
|
||
configureScopeWithState: (scope, state: RootState) => {
|
||
scope.setTag("user.plan", state.user.plan ?? "unknown");
|
||
scope.setTag("user.id", state.auth.userId ?? "anonymous");
|
||
scope.setTag("org.id", state.org.id ?? "none");
|
||
},
|
||
});
|
||
|
||
// store/index.ts
|
||
import { configureStore } from "@reduxjs/toolkit";
|
||
import { sentryReduxEnhancer } from "./sentry";
|
||
import rootReducer from "./reducers";
|
||
|
||
export const store = configureStore({
|
||
reducer: rootReducer,
|
||
enhancers: (getDefaultEnhancers) =>
|
||
getDefaultEnhancers().concat(sentryReduxEnhancer),
|
||
});
|
||
```
|
||
|
||
```typescript
|
||
// instrument.ts — init with normalizeDepth
|
||
import * as Sentry from "@sentry/react";
|
||
|
||
Sentry.init({
|
||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||
normalizeDepth: 10, // Required for deeply nested Redux state
|
||
integrations: [Sentry.browserTracingIntegration()],
|
||
tracesSampleRate: 1.0,
|
||
});
|
||
```
|
||
|
||
### Performance Considerations
|
||
|
||
Redux state can be very large. Keep in mind:
|
||
|
||
1. **State size:** Large state trees (thousands of items) will slow serialization and increase payload size. Use `stateTransformer` to return only the relevant slices.
|
||
2. **`normalizeDepth`:** Keep it as low as practical. `10` is usually sufficient for deeply nested state; avoid setting it to `Infinity`.
|
||
3. **`attachReduxState: false`:** For high-traffic production apps where payload size is a concern, disabling state attachment reduces each error event's size.
|
||
4. **`configureScopeWithState` cost:** This runs on every Redux dispatch. Keep the function fast — avoid heavy computations or deep object traversals.
|
||
|
||
---
|
||
|
||
## 2. Component Tracking & Performance
|
||
|
||
### A. React Component Name Annotation (Build-Time)
|
||
|
||
Replaces opaque CSS selectors in breadcrumbs, Session Replay, and performance spans with readable React component names.
|
||
|
||
**Before:** `button.en302zp1.app-191aavw.e16hd6vm2[role="button"]`
|
||
**After:** `CheckoutButton`
|
||
|
||
**Requirements:**
|
||
- SDK v7.91.0+
|
||
- Components must be in `.jsx` or `.tsx` files (`.js` and `.ts` are **not** annotated)
|
||
- esbuild is **not** supported
|
||
|
||
The bundler plugins inject `data-sentry-component` and `data-sentry-source-file` attributes at build time:
|
||
|
||
```html
|
||
<!-- Resulting DOM -->
|
||
<button
|
||
data-sentry-component="CheckoutButton"
|
||
data-sentry-source-file="CheckoutButton.tsx"
|
||
>
|
||
Checkout
|
||
</button>
|
||
```
|
||
|
||
**Enable via Vite (recommended):**
|
||
|
||
```typescript
|
||
// vite.config.ts
|
||
import { defineConfig } from "vite";
|
||
import react from "@vitejs/plugin-react";
|
||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
||
|
||
export default defineConfig({
|
||
plugins: [
|
||
react(),
|
||
sentryVitePlugin({
|
||
org: process.env.SENTRY_ORG,
|
||
project: process.env.SENTRY_PROJECT,
|
||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||
reactComponentAnnotation: {
|
||
enabled: true,
|
||
// Exclude components that cause "Passing unknown props on Fragment" errors
|
||
ignoredComponents: ["AnimationWrapper", "LayoutFragment"],
|
||
},
|
||
}),
|
||
],
|
||
build: {
|
||
sourcemap: "hidden",
|
||
},
|
||
});
|
||
```
|
||
|
||
**Enable via Babel plugin directly (without bundler plugin):**
|
||
|
||
```bash
|
||
npm install @sentry/babel-plugin-component-annotate --save-dev
|
||
```
|
||
|
||
```javascript
|
||
// babel.config.js
|
||
module.exports = {
|
||
plugins: ["@sentry/babel-plugin-component-annotate"],
|
||
};
|
||
```
|
||
|
||
**Bundler support:**
|
||
|
||
| Bundler | Component Annotation |
|
||
|---------|---------------------|
|
||
| Vite | ✅ Supported |
|
||
| Webpack | ✅ Supported |
|
||
| Rollup | ✅ Supported |
|
||
| esbuild | ❌ Not supported |
|
||
|
||
**What you gain:**
|
||
|
||
| Where | Before | After |
|
||
|-------|--------|-------|
|
||
| Breadcrumbs | `div.sc-abc123` | `ProductCard` |
|
||
| Session Replay | Unreadable selector | Search by `ProductCard` |
|
||
| Performance spans | Generic element | `CheckoutButton` render |
|
||
|
||
---
|
||
|
||
### B. `Sentry.withProfiler()` — React Profiler HOC
|
||
|
||
`withProfiler` wraps a component with the [React Profiler API](https://react.dev/reference/react/Profiler) to capture render timing as Sentry performance spans.
|
||
|
||
```typescript
|
||
import * as Sentry from "@sentry/react";
|
||
|
||
// Basic — display name inferred from component.displayName or .name
|
||
const ProfiledDashboard = Sentry.withProfiler(Dashboard);
|
||
|
||
// With explicit name (required for anonymous or arrow-function components)
|
||
const ProfiledWidget = Sentry.withProfiler(
|
||
({ data }) => <div>{data.title}</div>,
|
||
{ name: "DataWidget" }
|
||
);
|
||
|
||
// Class component decorator syntax
|
||
@Sentry.withProfiler
|
||
class ExpensiveList extends React.Component {
|
||
render() {
|
||
return <ul>{this.props.items.map(renderItem)}</ul>;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Options:**
|
||
|
||
| Option | Type | Default | Description |
|
||
|--------|------|---------|-------------|
|
||
| `name` | `string` | Component `displayName` or `name` | Display name shown in Sentry traces |
|
||
| `includeRender` | `boolean` | `true` | Track initial render phase |
|
||
| `includeUpdates` | `boolean` | `true` | Track re-render / update phases |
|
||
|
||
**What it captures:**
|
||
- Mount time (initial render duration)
|
||
- Update/re-render time per update
|
||
- Number of re-renders
|
||
- Component name in the Sentry performance trace waterfall
|
||
|
||
**Data appears in:** Sentry → Performance → Trace View, as child spans of the current transaction.
|
||
|
||
> **Requires tracing:** `browserTracingIntegration` must be in your `Sentry.init` integrations.
|
||
|
||
**When to use (and when not to):**
|
||
|
||
✅ Use on:
|
||
- Root-level route components (Dashboard, CheckoutFlow, UserProfile)
|
||
- Components with expensive render logic (large lists, complex calculations)
|
||
- Components that re-render frequently and may cause jank
|
||
|
||
❌ Do **not** use on:
|
||
- Every component in the tree — the overhead compounds
|
||
- Simple presentational/leaf components
|
||
- Components that render hundreds of times per second (e.g., animation frames)
|
||
|
||
**Performance overhead:** Each profiled component adds a small constant overhead per render cycle. Profile the 5–10 most performance-critical components, not the entire tree.
|
||
|
||
---
|
||
|
||
## 3. Source Maps
|
||
|
||
Source maps translate minified production stack traces back to your original source code. Without them, stack traces show obfuscated variable names and collapsed line numbers.
|
||
|
||
### Recommended: Sentry Wizard
|
||
|
||
The fastest path — automatically detects your bundler, installs the plugin, and configures auth:
|
||
|
||
> **You need to run this yourself** — the wizard opens a browser for login and requires interactive input that the agent can't handle. Copy-paste into your terminal:
|
||
>
|
||
> ```
|
||
> npx @sentry/wizard@latest -i sourcemaps
|
||
> ```
|
||
>
|
||
> It detects Vite/webpack/CRA, installs the appropriate Sentry bundler plugin, adds `SENTRY_AUTH_TOKEN` to `.env.sentry-build-plugin`, and configures `sourcemap: "hidden"` with `filesToDeleteAfterUpload`.
|
||
>
|
||
> **Once it finishes, come back and the agent will continue.**
|
||
|
||
If the user skips the wizard, proceed with manual bundler plugin setup below.
|
||
|
||
---
|
||
|
||
### How Debug IDs Work
|
||
|
||
Modern Sentry source map matching uses **Debug IDs** — unique identifiers injected by the bundler plugin into both the compiled `.js` bundle and the corresponding `.js.map` file. This makes source map matching reliable without needing to manage file names or release artifacts manually.
|
||
|
||
**Flow:**
|
||
1. Production build runs → bundler plugin injects a Debug ID into each `.js` and `.js.map` file
|
||
2. Plugin uploads `.js.map` files to Sentry with their Debug IDs
|
||
3. Error occurs in production → stack frame contains the Debug ID
|
||
4. Sentry uses the Debug ID to locate the exact source map → deobfuscates the trace
|
||
|
||
Debug IDs are more reliable than release-based matching (older approach) because they don't depend on consistent release naming or artifact upload timing.
|
||
|
||
---
|
||
|
||
### Vite Plugin
|
||
|
||
**Minimum SDK:** `@sentry/vite-plugin` `2.0.0` / `@sentry/react` `7.47.0`
|
||
|
||
```bash
|
||
npm install @sentry/vite-plugin --save-dev
|
||
```
|
||
|
||
```typescript
|
||
// vite.config.ts
|
||
import { defineConfig } from "vite";
|
||
import react from "@vitejs/plugin-react";
|
||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
||
|
||
export default defineConfig({
|
||
build: {
|
||
// "hidden" generates source maps but strips the `//# sourceMappingURL=` comment
|
||
// from bundles, so browsers won't load them — they're only for Sentry.
|
||
sourcemap: "hidden",
|
||
},
|
||
plugins: [
|
||
react(),
|
||
// sentryVitePlugin MUST come after all other plugins
|
||
sentryVitePlugin({
|
||
org: process.env.SENTRY_ORG,
|
||
project: process.env.SENTRY_PROJECT,
|
||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||
|
||
// Enable React component name annotation at the same time
|
||
reactComponentAnnotation: {
|
||
enabled: true,
|
||
},
|
||
|
||
sourcemaps: {
|
||
// Delete .map files from dist/ after uploading to Sentry
|
||
// so they're not deployed to your CDN/server
|
||
filesToDeleteAfterUpload: [
|
||
"./**/*.map",
|
||
".*/**/public/**/*.map",
|
||
"./dist/**/client/**/*.map",
|
||
],
|
||
},
|
||
}),
|
||
],
|
||
});
|
||
```
|
||
|
||
> ⚠️ Place `sentryVitePlugin` **after** all other plugins (especially `@vitejs/plugin-react`) to ensure correct source map generation.
|
||
|
||
---
|
||
|
||
### Webpack Plugin
|
||
|
||
**Minimum SDK:** `@sentry/webpack-plugin` `2.0.0` / `@sentry/react` `7.47.0`
|
||
|
||
```bash
|
||
npm install @sentry/webpack-plugin --save-dev
|
||
```
|
||
|
||
```javascript
|
||
// webpack.config.js
|
||
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");
|
||
|
||
module.exports = {
|
||
// "hidden-source-map" generates source maps without the `//# sourceMappingURL=`
|
||
// reference comment, so they won't be served publicly.
|
||
devtool: "hidden-source-map",
|
||
|
||
plugins: [
|
||
sentryWebpackPlugin({
|
||
org: process.env.SENTRY_ORG,
|
||
project: process.env.SENTRY_PROJECT,
|
||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||
sourcemaps: {
|
||
filesToDeleteAfterUpload: [
|
||
"./**/*.map",
|
||
"./build/static/**/*.map",
|
||
],
|
||
},
|
||
}),
|
||
],
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### Create React App
|
||
|
||
CRA has limited configuration access. Two approaches:
|
||
|
||
**Option A: CRACO (recommended — no ejection)**
|
||
|
||
```bash
|
||
npm install @craco/craco @sentry/webpack-plugin --save-dev
|
||
```
|
||
|
||
```javascript
|
||
// craco.config.js
|
||
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");
|
||
|
||
module.exports = {
|
||
webpack: {
|
||
configure: (webpackConfig) => {
|
||
webpackConfig.devtool = "hidden-source-map";
|
||
webpackConfig.plugins.push(
|
||
sentryWebpackPlugin({
|
||
org: process.env.SENTRY_ORG,
|
||
project: process.env.SENTRY_PROJECT,
|
||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||
sourcemaps: {
|
||
filesToDeleteAfterUpload: ["./build/static/**/*.map"],
|
||
},
|
||
})
|
||
);
|
||
return webpackConfig;
|
||
},
|
||
},
|
||
};
|
||
```
|
||
|
||
Update `package.json` scripts:
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"start": "craco start",
|
||
"build": "craco build"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Option B: Eject (`npm run eject`)**
|
||
|
||
After ejecting, edit `config/webpack.config.js` directly — same as the regular webpack setup above. Ejection is irreversible; prefer CRACO.
|
||
|
||
---
|
||
|
||
### Manual Upload with `sentry-cli`
|
||
|
||
Use when you can't use a bundler plugin (e.g., legacy toolchain, pre-built artifacts):
|
||
|
||
```bash
|
||
npm install @sentry/cli --save-dev
|
||
```
|
||
|
||
```bash
|
||
# Upload source maps after a production build
|
||
npx sentry-cli sourcemaps upload \
|
||
--org $SENTRY_ORG \
|
||
--project $SENTRY_PROJECT \
|
||
--auth-token $SENTRY_AUTH_TOKEN \
|
||
./build
|
||
```
|
||
|
||
**`.sentryclirc` configuration file (alternative to env vars):**
|
||
|
||
```ini
|
||
[defaults]
|
||
org = my-org-slug
|
||
project = my-project-slug
|
||
url = https://sentry.io/
|
||
```
|
||
|
||
```bash
|
||
# With .sentryclirc, no --org/--project flags needed:
|
||
npx sentry-cli sourcemaps upload --auth-token $SENTRY_AUTH_TOKEN ./build
|
||
```
|
||
|
||
> ⚠️ Never commit `SENTRY_AUTH_TOKEN` to source control. Always read it from environment variables.
|
||
|
||
**CI/CD pattern (GitHub Actions):**
|
||
|
||
```yaml
|
||
- name: Build
|
||
run: npm run build
|
||
|
||
- name: Upload source maps to Sentry
|
||
env:
|
||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||
SENTRY_ORG: my-org-slug
|
||
SENTRY_PROJECT: my-react-app
|
||
run: |
|
||
npx sentry-cli sourcemaps upload \
|
||
--org $SENTRY_ORG \
|
||
--project $SENTRY_PROJECT \
|
||
--auth-token $SENTRY_AUTH_TOKEN \
|
||
./build
|
||
|
||
- name: Delete source maps from build artifacts
|
||
run: find ./build -name "*.map" -delete
|
||
|
||
- name: Deploy
|
||
run: # your deploy step
|
||
```
|
||
|
||
---
|
||
|
||
### Environment Variables for Source Maps
|
||
|
||
Store credentials in `.env.sentry-build-plugin` (auto-loaded by Sentry bundler plugins) or pass as CI/CD secrets:
|
||
|
||
```bash
|
||
# .env.sentry-build-plugin ← auto-loaded by @sentry/vite-plugin and @sentry/webpack-plugin
|
||
# ADD THIS FILE TO .gitignore — never commit it
|
||
SENTRY_AUTH_TOKEN=sntrys_eyJ...
|
||
SENTRY_ORG=my-org-slug
|
||
SENTRY_PROJECT=my-project-slug
|
||
```
|
||
|
||
**Required token permissions:**
|
||
|
||
The `SENTRY_AUTH_TOKEN` must have:
|
||
- **Project:** Read & Write
|
||
- **Release:** Admin
|
||
|
||
---
|
||
|
||
### Source Map Security
|
||
|
||
| Strategy | How |
|
||
|----------|-----|
|
||
| Don't expose maps to browsers | Use `sourcemap: "hidden"` (Vite) or `devtool: "hidden-source-map"` (webpack) |
|
||
| Delete maps after upload | Use `filesToDeleteAfterUpload` in the plugin config |
|
||
| Block map access at CDN/server | Configure your server to return 403 for `.js.map` requests |
|
||
|
||
All three should be used together for maximum security.
|
||
|
||
---
|
||
|
||
### Troubleshooting Source Maps
|
||
|
||
| Problem | Likely Cause | Fix |
|
||
|---------|-------------|-----|
|
||
| Stack traces still minified | Source maps not uploaded, or Debug IDs missing | Rebuild with production config, re-run wizard or plugin |
|
||
| Maps not applied to old errors | Maps uploaded after errors occurred | Always upload maps **before** deploying — ideally in the same CI step |
|
||
| "SourceMapDevToolPlugin" stripping sources | `noSources: true` in your webpack SourceMapDevToolPlugin | Remove `noSources: true` option |
|
||
| Plugin only uploads once | Running in `--watch` or dev mode | Plugin only uploads during production builds (`NODE_ENV=production`) |
|
||
| SENTRY_AUTH_TOKEN not found | Missing env variable | Check `.env.sentry-build-plugin` exists and is not gitignored incorrectly |
|
||
| Component names still not showing | `.js`/`.ts` files instead of `.jsx`/`.tsx` | Rename files or use the Babel plugin directly on all JSX transform targets |
|
||
|
||
---
|
||
|
||
## 4. Default Integrations
|
||
|
||
These integrations are **automatically enabled** for every `@sentry/react` installation. No configuration required.
|
||
|
||
| Integration | Name Constant | What It Does |
|
||
|-------------|--------------|--------------|
|
||
| Breadcrumbs | `breadcrumbsIntegration` | Captures breadcrumbs from DOM events (clicks, inputs), XHR, fetch, console calls, history navigation |
|
||
| Browser API Errors | `browserApiErrorsIntegration` | Wraps `setTimeout`, `setInterval`, `requestAnimationFrame`, `addEventListener`, `removeEventListener` in try/catch so errors inside them are captured |
|
||
| Browser Session | `browserSessionIntegration` | Tracks session health (healthy vs. crashed) for Release Health metrics |
|
||
| Dedupe | `dedupeIntegration` | Prevents duplicate error events from being sent — deduplicates based on error type, message, and stack trace |
|
||
| Function to String | `functionToStringIntegration` | Preserves original function `.toString()` output after SDK instrumentation, so stack traces show readable names |
|
||
| Global Handlers | `globalHandlersIntegration` | Listens to `window.onerror` (uncaught exceptions) and `window.onunhandledrejection` (unhandled promise rejections) |
|
||
| HTTP Context | `httpContextIntegration` | Attaches current URL, referrer, and user-agent to every event |
|
||
| Inbound Filters | `inboundFiltersIntegration` | Filters known-noisy events by error type, message, or URL (e.g., browser extension errors, localhost-only errors) |
|
||
| Linked Errors | `linkedErrorsIntegration` | Follows JavaScript `error.cause` chains and attaches nested errors as linked issues — also used for React component stack linkage |
|
||
|
||
### Customizing a Default Integration's Options
|
||
|
||
Pass the integration explicitly in `integrations` — it overrides the default instance:
|
||
|
||
```typescript
|
||
Sentry.init({
|
||
dsn: "___PUBLIC_DSN___",
|
||
integrations: [
|
||
// Override breadcrumbs to disable console capturing but keep DOM/fetch/XHR
|
||
Sentry.breadcrumbsIntegration({
|
||
console: false, // don't capture console.log as breadcrumbs
|
||
dom: true,
|
||
fetch: true,
|
||
history: true,
|
||
xhr: true,
|
||
}),
|
||
|
||
// Only capture unhandled exceptions; handle rejections manually
|
||
Sentry.globalHandlersIntegration({
|
||
onerror: true,
|
||
onunhandledrejection: false,
|
||
}),
|
||
],
|
||
});
|
||
```
|
||
|
||
### Removing a Default Integration
|
||
|
||
Use the function form of `integrations` to filter out what you don't want:
|
||
|
||
```typescript
|
||
Sentry.init({
|
||
dsn: "___PUBLIC_DSN___",
|
||
integrations: (integrations) => {
|
||
// Remove deduplication (e.g., if you want every occurrence recorded separately)
|
||
return integrations.filter(
|
||
(integration) => integration.name !== "Dedupe"
|
||
);
|
||
},
|
||
});
|
||
```
|
||
|
||
Common integration names to filter:
|
||
- `"Dedupe"` — deduplication
|
||
- `"Breadcrumbs"` — all automatic breadcrumbs
|
||
- `"GlobalHandlers"` — window.onerror / unhandledrejection
|
||
- `"LinkedErrors"` — error cause chain following
|
||
- `"HttpContext"` — URL/referrer attachment
|
||
- `"InboundFilters"` — built-in noise filtering
|
||
|
||
### Disabling ALL Default Integrations
|
||
|
||
```typescript
|
||
Sentry.init({
|
||
dsn: "___PUBLIC_DSN___",
|
||
defaultIntegrations: false,
|
||
// Build your own set from scratch:
|
||
integrations: [
|
||
Sentry.globalHandlersIntegration(),
|
||
Sentry.browserTracingIntegration(),
|
||
Sentry.dedupeIntegration(),
|
||
],
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Optional Integrations
|
||
|
||
These are available but **must be explicitly added** to your `integrations` array.
|
||
|
||
### Performance & Tracing
|
||
|
||
| Integration | Min SDK | Description |
|
||
|-------------|---------|-------------|
|
||
| `browserTracingIntegration()` | 8.0.0 | Page load tracing, navigation tracing, automatic span creation for fetch/XHR, Core Web Vitals (LCP, FID, CLS), distributed tracing headers |
|
||
| `browserProfilingIntegration()` | 10.27.0 (Beta) | JS Self-Profiling API — captures call stacks at 100Hz in Chromium browsers. **Requires** `Document-Policy: js-profiling` HTTP header |
|
||
|
||
### Session Replay
|
||
|
||
| Integration | Min SDK | Description |
|
||
|-------------|---------|-------------|
|
||
| `replayIntegration()` | 7.27.0 | Session Replay — records DOM mutations, network requests, console output. Configured with `replaysSessionSampleRate` and `replaysOnErrorSampleRate` |
|
||
| `replayCanvasIntegration()` | 7.98.0 | Extends `replayIntegration` to record `<canvas>` elements in replays |
|
||
|
||
### Logging
|
||
|
||
| Integration | Min SDK | Description |
|
||
|-------------|---------|-------------|
|
||
| `consoleLoggingIntegration()` | 9.41.0 | Automatically forwards `console.log/warn/error/info/debug` calls as structured Sentry logs. Requires `enableLogs: true` |
|
||
|
||
```typescript
|
||
Sentry.init({
|
||
enableLogs: true,
|
||
integrations: [
|
||
Sentry.consoleLoggingIntegration({
|
||
levels: ["warn", "error"], // Only capture warnings and errors
|
||
}),
|
||
],
|
||
});
|
||
```
|
||
|
||
### User Feedback
|
||
|
||
| Integration | Min SDK | Description |
|
||
|-------------|---------|-------------|
|
||
| `feedbackIntegration()` | 7.85.0 | Floating feedback button + form (bottom-right). Supports screenshots, custom theming, programmatic control |
|
||
| `feedbackModalIntegration()` | 7.85.0 | Modal dialog variant of the feedback form |
|
||
| `feedbackScreenshotIntegration()` | 8.0.0 | Adds screenshot capture capability to the feedback widget |
|
||
|
||
### Error Enhancement
|
||
|
||
| Integration | Min SDK | Description |
|
||
|-------------|---------|-------------|
|
||
| `extraErrorDataIntegration()` | 5.16.0 | Attaches non-standard properties on `Error` objects (e.g., `error.code`, `error.statusCode`, custom fields) as extra context |
|
||
| `contextLinesIntegration()` | 7.47.0 | Shows source code lines above and below the erroring line in the stack trace (requires source maps) |
|
||
| `httpClientIntegration()` | 7.50.0 | Captures failed HTTP requests (4xx/5xx responses) as Sentry error events, with request/response bodies. Opt-in because it may capture PII |
|
||
| `reportingObserverIntegration()` | 5.9.0 | Captures [Reporting Observer API](https://developer.mozilla.org/en-US/docs/Web/API/ReportingObserver) events (deprecation warnings, browser interventions, CSP violations) |
|
||
| `captureConsoleIntegration()` | 3.3.0 | Captures `console.error`/`console.warn` calls as Sentry issues (not logs). Legacy alternative to `consoleLoggingIntegration` |
|
||
|
||
### Stack Frame Rewriting
|
||
|
||
| Integration | Min SDK | Description |
|
||
|-------------|---------|-------------|
|
||
| `rewriteFramesIntegration()` | 5.7.0 | Rewrites stack frame file paths — useful for normalizing paths in monorepos, Docker containers, or when paths differ between build and deploy environments |
|
||
|
||
---
|
||
|
||
### `httpClientIntegration` — Full Setup
|
||
|
||
Captures HTTP requests that fail (4xx/5xx) as error events, with the request URL, method, status code, and optionally request/response bodies.
|
||
|
||
```typescript
|
||
Sentry.init({
|
||
dsn: "___PUBLIC_DSN___",
|
||
integrations: [
|
||
Sentry.httpClientIntegration({
|
||
// Capture errors for these HTTP status code ranges
|
||
failedRequestStatusCodes: [[400, 499], [500, 599]],
|
||
|
||
// Only capture errors for these URL patterns
|
||
failedRequestTargets: [
|
||
"https://api.myapp.com",
|
||
/^https:\/\/internal\.service\//,
|
||
],
|
||
}),
|
||
],
|
||
dataCollection: {
|
||
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
|
||
// https://docs.sentry.io/platforms/javascript/guides/react/configuration/options/#dataCollection
|
||
// userInfo: false,
|
||
// httpBodies: [],
|
||
},
|
||
});
|
||
```
|
||
|
||
### Adding Integrations After Init
|
||
|
||
If you need to add an integration after `Sentry.init()` has been called (e.g., after user consent):
|
||
|
||
```typescript
|
||
// After the user accepts analytics cookies:
|
||
Sentry.addIntegration(Sentry.replayIntegration({
|
||
maskAllText: true,
|
||
blockAllMedia: true,
|
||
}));
|
||
```
|
||
|
||
### Lazy Loading Integrations (Code Splitting)
|
||
|
||
```typescript
|
||
// Dynamic import (works with any bundler)
|
||
import("@sentry/browser").then((lazySentry) => {
|
||
Sentry.addIntegration(lazySentry.replayIntegration());
|
||
});
|
||
|
||
// CDN lazyLoadIntegration() — for Sentry loader script environments
|
||
async function enableFeedback() {
|
||
try {
|
||
const feedbackIntegration =
|
||
await Sentry.lazyLoadIntegration("feedbackIntegration");
|
||
Sentry.addIntegration(feedbackIntegration({ colorScheme: "system" }));
|
||
} catch (e) {
|
||
// Ad-blockers or network failures — fail gracefully
|
||
console.warn("Could not load Sentry feedback integration", e);
|
||
}
|
||
}
|
||
```
|
||
|
||
Lazy-loadable integrations: `replayIntegration`, `replayCanvasIntegration`, `feedbackIntegration`, `feedbackModalIntegration`, `feedbackScreenshotIntegration`, `captureConsoleIntegration`, `contextLinesIntegration`, `linkedErrorsIntegration`, `dedupeIntegration`, `extraErrorDataIntegration`, `httpClientIntegration`, `reportingObserverIntegration`, `rewriteFramesIntegration`, `browserProfilingIntegration`
|
||
|
||
---
|
||
|
||
## 6. Build Tool Detection & Environment Variables
|
||
|
||
### Detecting the Build Tool
|
||
|
||
```bash
|
||
# Run from project root
|
||
ls vite.config.ts vite.config.js webpack.config.js webpack.config.ts \
|
||
craco.config.js next.config.js next.config.ts 2>/dev/null
|
||
|
||
cat package.json | grep -E '"vite"|"react-scripts"|"webpack"|"@craco"'
|
||
```
|
||
|
||
| File/Package Found | Build Tool |
|
||
|-------------------|------------|
|
||
| `vite.config.*` or `"vite"` in deps | Vite |
|
||
| `"react-scripts"` in deps | Create React App |
|
||
| `craco.config.js` or `"@craco/craco"` | CRA + CRACO |
|
||
| `webpack.config.*` or `"webpack"` in deps | Custom Webpack |
|
||
| `next.config.*` or `"next"` in deps | Next.js (use `@sentry/nextjs` instead) |
|
||
|
||
---
|
||
|
||
### DSN Environment Variable Patterns
|
||
|
||
| Build Tool | Variable Name | How to Access in Code |
|
||
|------------|--------------|----------------------|
|
||
| Vite | `VITE_SENTRY_DSN` | `import.meta.env.VITE_SENTRY_DSN` |
|
||
| Create React App | `REACT_APP_SENTRY_DSN` | `process.env.REACT_APP_SENTRY_DSN` |
|
||
| Custom Webpack | `SENTRY_DSN` | `process.env.SENTRY_DSN` (requires `DefinePlugin`) |
|
||
| Any | `SENTRY_DSN` | Build-time injection (not available at runtime in browser) |
|
||
|
||
**Vite — `.env` file:**
|
||
```bash
|
||
# .env.production
|
||
VITE_SENTRY_DSN=https://<key>@<org>.ingest.sentry.io/<id>
|
||
VITE_SENTRY_ENVIRONMENT=production
|
||
```
|
||
|
||
```typescript
|
||
// instrument.ts
|
||
Sentry.init({
|
||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT ?? "development",
|
||
});
|
||
```
|
||
|
||
**Create React App — `.env.production` file:**
|
||
```bash
|
||
# .env.production (committed — DSN is public)
|
||
REACT_APP_SENTRY_DSN=https://<key>@<org>.ingest.sentry.io/<id>
|
||
REACT_APP_SENTRY_ENVIRONMENT=production
|
||
```
|
||
|
||
```typescript
|
||
// instrument.ts
|
||
Sentry.init({
|
||
dsn: process.env.REACT_APP_SENTRY_DSN,
|
||
environment: process.env.REACT_APP_SENTRY_ENVIRONMENT ?? "development",
|
||
});
|
||
```
|
||
|
||
**Custom Webpack — `webpack.config.js` with `DefinePlugin`:**
|
||
```javascript
|
||
const webpack = require("webpack");
|
||
|
||
module.exports = {
|
||
plugins: [
|
||
new webpack.DefinePlugin({
|
||
"process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN),
|
||
"process.env.SENTRY_ENVIRONMENT": JSON.stringify(
|
||
process.env.NODE_ENV ?? "development"
|
||
),
|
||
}),
|
||
],
|
||
};
|
||
```
|
||
|
||
```typescript
|
||
// instrument.ts
|
||
Sentry.init({
|
||
dsn: process.env.SENTRY_DSN,
|
||
environment: process.env.SENTRY_ENVIRONMENT,
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### Conditional Initialization (Development vs Production)
|
||
|
||
Prevent Sentry from running in local development to avoid polluting your issue inbox with dev noise:
|
||
|
||
```typescript
|
||
// instrument.ts
|
||
import * as Sentry from "@sentry/react";
|
||
|
||
const IS_PRODUCTION =
|
||
import.meta.env.PROD || // Vite
|
||
process.env.NODE_ENV === "production"; // webpack / CRA
|
||
|
||
if (IS_PRODUCTION) {
|
||
Sentry.init({
|
||
dsn: import.meta.env.VITE_SENTRY_DSN, // or your env var pattern
|
||
environment: "production",
|
||
integrations: [
|
||
Sentry.browserTracingIntegration(),
|
||
Sentry.replayIntegration({
|
||
maskAllText: false,
|
||
blockAllMedia: false,
|
||
}),
|
||
],
|
||
tracesSampleRate: 0.2, // 20% in production
|
||
replaysSessionSampleRate: 0.1,
|
||
replaysOnErrorSampleRate: 1.0,
|
||
});
|
||
} else {
|
||
// In development: optionally init with verbose debug and full sampling
|
||
Sentry.init({
|
||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||
environment: "development",
|
||
debug: true, // Verbose SDK logging
|
||
tracesSampleRate: 1.0, // 100% in dev so nothing is missed
|
||
integrations: [Sentry.browserTracingIntegration()],
|
||
// No Replay in dev — too noisy
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Complete `instrument.ts` Reference — All Features
|
||
|
||
A kitchen-sink example combining every feature:
|
||
|
||
```typescript
|
||
// src/instrument.ts
|
||
import * as Sentry from "@sentry/react";
|
||
|
||
Sentry.init({
|
||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||
environment: import.meta.env.VITE_APP_ENV ?? "production",
|
||
release: import.meta.env.VITE_APP_VERSION,
|
||
|
||
// ── Integrations ─────────────────────────────────────────────
|
||
integrations: [
|
||
// Tracing (navigation + API + Core Web Vitals)
|
||
Sentry.browserTracingIntegration(),
|
||
|
||
// Session Replay
|
||
Sentry.replayIntegration({
|
||
maskAllText: false,
|
||
blockAllMedia: false,
|
||
}),
|
||
|
||
// Profiling (Beta — Chromium only, needs Document-Policy header)
|
||
// Sentry.browserProfilingIntegration(),
|
||
|
||
// Structured logging (requires enableLogs: true)
|
||
Sentry.consoleLoggingIntegration({
|
||
levels: ["warn", "error"],
|
||
}),
|
||
|
||
// Capture HTTP 4xx/5xx as Sentry errors
|
||
Sentry.httpClientIntegration({
|
||
failedRequestStatusCodes: [[400, 499], [500, 599]],
|
||
}),
|
||
|
||
// Show source lines in stack traces (requires source maps)
|
||
Sentry.contextLinesIntegration(),
|
||
|
||
// Capture non-standard Error properties
|
||
Sentry.extraErrorDataIntegration(),
|
||
|
||
// User feedback widget
|
||
Sentry.feedbackIntegration({
|
||
colorScheme: "system",
|
||
showBranding: false,
|
||
triggerLabel: "Report a Bug",
|
||
}),
|
||
],
|
||
|
||
// ── Tracing ───────────────────────────────────────────────────
|
||
tracesSampleRate: 0.2,
|
||
tracePropagationTargets: [
|
||
"localhost",
|
||
/^https:\/\/api\.myapp\.com/,
|
||
],
|
||
|
||
// ── Session Replay ────────────────────────────────────────────
|
||
replaysSessionSampleRate: 0.1,
|
||
replaysOnErrorSampleRate: 1.0,
|
||
|
||
// ── Profiling ─────────────────────────────────────────────────
|
||
profileSessionSampleRate: 1.0,
|
||
|
||
// ── Logging ───────────────────────────────────────────────────
|
||
enableLogs: true,
|
||
beforeSendLog: (log) => {
|
||
if (log.level === "debug") return null; // Drop debug logs
|
||
if (log.attributes?.password) delete log.attributes.password;
|
||
return log;
|
||
},
|
||
|
||
// ── Redux ─────────────────────────────────────────────────────
|
||
normalizeDepth: 10,
|
||
|
||
// ── Filtering ─────────────────────────────────────────────────
|
||
ignoreErrors: [
|
||
"ResizeObserver loop limit exceeded",
|
||
/^Loading chunk \d+ failed/,
|
||
],
|
||
denyUrls: [
|
||
/extensions\//i,
|
||
/^chrome:\/\//i,
|
||
],
|
||
|
||
beforeSend(event) {
|
||
// Strip credit card numbers from messages just in case
|
||
if (event.message) {
|
||
event.message = event.message.replace(/\b\d{16}\b/g, "[CARD]");
|
||
}
|
||
return event;
|
||
},
|
||
});
|
||
```
|
||
|
||
```typescript
|
||
// store/index.ts — wire in Redux enhancer
|
||
import { configureStore } from "@reduxjs/toolkit";
|
||
import * as Sentry from "@sentry/react";
|
||
|
||
export const store = configureStore({
|
||
reducer: rootReducer,
|
||
enhancers: (getDefaultEnhancers) =>
|
||
getDefaultEnhancers().concat(
|
||
Sentry.createReduxEnhancer({
|
||
stateTransformer: (state) => ({ ...state, auth: null }),
|
||
configureScopeWithState: (scope, state) => {
|
||
scope.setTag("user.plan", state.user.plan);
|
||
},
|
||
})
|
||
),
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Quick Reference
|
||
|
||
```typescript
|
||
// ── Redux ─────────────────────────────────────────────────────
|
||
Sentry.createReduxEnhancer({
|
||
actionTransformer: (action) => action | null,
|
||
stateTransformer: (state) => state | null,
|
||
configureScopeWithState: (scope, state) => void,
|
||
attachReduxState: true,
|
||
})
|
||
// normalizeDepth goes in Sentry.init(), not createReduxEnhancer()
|
||
|
||
// ── Component Tracking ────────────────────────────────────────
|
||
Sentry.withProfiler(Component, { name?, includeRender?, includeUpdates? })
|
||
sentryVitePlugin({ reactComponentAnnotation: { enabled: true, ignoredComponents: [] } })
|
||
// Babel plugin direct: @sentry/babel-plugin-component-annotate
|
||
|
||
// ── Source Maps ───────────────────────────────────────────────
|
||
npx @sentry/wizard@latest -i sourcemaps // automated setup
|
||
// Vite: build.sourcemap = "hidden" + sentryVitePlugin()
|
||
// Webpack: devtool: "hidden-source-map" + sentryWebpackPlugin()
|
||
// CRA: use CRACO + sentryWebpackPlugin
|
||
// Manual: npx sentry-cli sourcemaps upload --auth-token $TOKEN ./dist
|
||
|
||
// ── Integrations (opt-in) ─────────────────────────────────────
|
||
Sentry.browserTracingIntegration() // Tracing + Core Web Vitals
|
||
Sentry.replayIntegration() // Session Replay
|
||
Sentry.browserProfilingIntegration() // JS Profiler (Beta, Chromium only)
|
||
Sentry.consoleLoggingIntegration() // console → Sentry logs
|
||
Sentry.feedbackIntegration() // Feedback widget
|
||
Sentry.httpClientIntegration() // 4xx/5xx as errors
|
||
Sentry.contextLinesIntegration() // Source lines in stack traces
|
||
Sentry.extraErrorDataIntegration() // Non-standard Error properties
|
||
Sentry.reportingObserverIntegration() // Browser deprecation/CSP reports
|
||
Sentry.rewriteFramesIntegration() // Rewrite stack frame paths
|
||
Sentry.captureConsoleIntegration() // console → Sentry issues (legacy)
|
||
|
||
// ── Remove a Default Integration ─────────────────────────────
|
||
Sentry.init({
|
||
integrations: (integrations) =>
|
||
integrations.filter((i) => i.name !== "Dedupe"),
|
||
})
|
||
|
||
// ── Add Integration After Init ────────────────────────────────
|
||
Sentry.addIntegration(Sentry.replayIntegration())
|
||
|
||
// ── Environment Variables ─────────────────────────────────────
|
||
// Vite: import.meta.env.VITE_SENTRY_DSN
|
||
// CRA: process.env.REACT_APP_SENTRY_DSN
|
||
// Webpack: process.env.SENTRY_DSN (via DefinePlugin)
|
||
```
|