BUILT INTO EVERY GOODSPEED APP
Sentry Error Monitoring + Crash-Free SLO Gate
Sentry captures unhandled errors behind an app-wide boundary, with breadcrumbs and PII-redacted context. A CI helper queries the Sentry Sessions API and can block an EAS store submission when the 24-hour crash-free rate falls below the configured floor (99.5% iOS, 99.0% Android by default).
- Tier: Common
- Status: Config-toggled
- Config: features.analytics.crashReporting
WHY IT MATTERS
Crash reporting only earns its place if it is on before the crash, redacts what it should not see, and produces readable stack traces. The template wires @sentry/react-native in lib/sentry.ts with a null-safe pattern: Sentry.init runs only when features.analytics.crashReporting is true and EXPO_PUBLIC_SENTRY_DSN is set, and otherwise every export is a no-op so a build with no DSN ships clean. Init sets tracesSampleRate to 0.2 and enables auto session tracking. Errors flow through captureException, which wraps context in a scope and runs it through sanitizeData first, so any key matching a sensitive-key regex (password, token, secret, authorization, cookie, and more) becomes [REDACTED] before it leaves the device. A SentryErrorBoundary wraps the whole app tree, so a render-phase exception shows a fallback instead of a white screen.
Two things make the signal trustworthy. Source maps are uploaded during EAS builds by the @sentry/react-native/expo config plugin, keyed to your Sentry org and project, so production stack traces point at real lines rather than minified noise. And release health is gated in CI: scripts/crash-free-helpers.ts queries the Sentry Sessions API for the crash-free rate per platform and checks it against gasConfig.monitoring.crashFreeThresholds (99.5% iOS, 99.0% Android in production), which can block an EAS store submission when a release is regressing. Breadcrumbs are written at every key action (auth, navigation, purchase, offline) so an event arrives with the trail that led to it, not just the final throw.
HOW IT IS WIRED
Real code from the GAS template
The code below is drawn from lib/sentry.ts in the gas-template repository. This is the code your generated app gets, not pseudocode, not a description of intent.
// lib/sentry.ts
const dsn = process.env.EXPO_PUBLIC_SENTRY_DSN ?? '';
const isEnabled = gasConfig.features.analytics.crashReporting && !!dsn;
const SENSITIVE_KEYS =
/password|token|secret|key|authorization|cookie|credential|apiKey|api_key|authToken|auth_token|access_code|private_key|client_secret/i;
export function sanitizeData(data: Record<string, unknown>): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const [k, v] of Object.entries(data)) {
out[k] = SENSITIVE_KEYS.test(k) ? '[REDACTED]' : v;
}
return out;
}
if (isEnabled) {
Sentry.init({
dsn,
tracesSampleRate: 0.2,
environment: __DEV__ ? 'development' : 'production',
enableAutoSessionTracking: true,
attachStacktrace: true,
});
}
export function captureException(error: unknown, context?: Record<string, unknown>): void {
if (!isEnabled) return;
if (context) {
Sentry.withScope((scope) => {
Object.entries(sanitizeData(context)).forEach(([k, v]) => scope.setExtra(k, v));
Sentry.captureException(error);
});
} else {
Sentry.captureException(error);
}
}Source: goodspeed-apps/gas-template → lib/sentry.ts
HONEST LIMITS
When Sentry Error Monitoring + Crash-Free SLO Gate is the wrong choice
Read the beforeSend hook before you rely on Sentry for offline diagnostics. To cut noise, it drops any event whose exception is a TypeError with the message Network request failed and which also carries an offline breadcrumb. That removes the expected background-sync chatter, but it can also hide a genuine offline-triggered network bug, because the filter matches on the error shape, not on whether the failure was intended. If offline reliability is core to your app, loosen that filter so real failures still reach Sentry. Also note that isEnabled is a module-level constant evaluated once at import. SentryErrorBoundary is assigned null when crash reporting is off or the DSN is empty at load time, so adding the DSN or flipping the flag later does not activate the boundary without a full reload. Set the DSN and the flag before the first import in any environment where you actually want capture, rather than toggling them at runtime and expecting the boundary to wake up.
Tier: Common · Config-toggled
Evaluate your use case
Check whether sentry error monitoring + crash-free slo gate aligns with your target audience, platform constraints, and regulatory environment before enabling it.
Audit the config
The `features.analytics.crashReporting` flag controls this feature. Set it to false in gas.config.ts to disable the feature entirely with no residual code paths.
Seek alternatives
If the built-in implementation does not fit, the generated codebase is standard React Native + Expo code. Any library in the Expo ecosystem can replace the default.
APPS USING THIS FEATURE
Every generated Goodspeed app includes sentry error monitoring + crash-free slo gate. Browse the ideas catalog to see apps across all categories that ship with this feature wired in.
CAPABILITIES
Sentry Error Monitoring + Crash-Free SLO Gate capability breakdown
Concrete dimensions of what the built-in sentry error monitoring + crash-free slo gate implementation covers. These reflect the actual template code, not a marketing summary.
| Item | Description | Strength |
|---|---|---|
| Null-safe init | Sentry.init runs only when crashReporting is true and EXPO_PUBLIC_SENTRY_DSN is set; otherwise every export is a no-op, so a build without a DSN carries no Sentry runtime. | DSN-gated |
| App-wide error boundary | SentryErrorBoundary wraps the entire app root in app/_layout.tsx, catching render-phase exceptions that would otherwise leave a blank screen and reporting them. | Built in |
| PII redaction | sanitizeData runs a SENSITIVE_KEYS regex over breadcrumb data and captureException context, replacing matched keys (password, token, secret, and more) with [REDACTED] before send. | Redacted |
| Readable stack traces | The @sentry/react-native/expo config plugin uploads source maps during EAS builds, keyed to SENTRY_ORG and SENTRY_PROJECT, so production traces resolve to real source lines. | Source maps |
| Crash-free SLO gate | scripts/crash-free-helpers.ts queries the Sentry Sessions API per platform and checks the rate against gasConfig.monitoring.crashFreeThresholds, able to block a regressing EAS submission. | CI gate |
COMMON QUESTIONS
Does Sentry receive passwords or tokens from my app?
Not from the paths the template controls. sanitizeData applies a SENSITIVE_KEYS regex to every breadcrumb data object and every captureException context object, replacing values under keys like password, token, secret, authorization, cookie, credential, apiKey, access_code, private_key, and client_secret with [REDACTED] before the event is sent. What is not scrubbed by this function is the error message and stack frames themselves, so avoid interpolating secrets into thrown error strings, since those reach Sentry verbatim.
Will my production stack traces be readable or minified?
Readable, for EAS builds. When EXPO_PUBLIC_SENTRY_DSN is set, app.config.js adds the @sentry/react-native/expo config plugin, which uploads source maps during the EAS build pipeline keyed to your Sentry org and project. That lets Sentry symbolicate production stack traces back to your real source lines. Local or dev builds made without that env var do not upload source maps, so traces from those builds stay minified.
How much Sentry volume and cost does this generate?
Error events are always captured when enabled; tracesSampleRate is set to 0.2, so 20% of performance transactions are sampled rather than all of them, which keeps tracing volume down. The beforeSend hook also drops a specific class of offline network errors to cut noise. Auto session tracking produces one session record per app launch, which is what powers the crash-free rate the SLO gate reads. Tune tracesSampleRate down if transaction volume becomes the cost driver.
Does it work on Expo Web?
@sentry/react-native targets native, so web support is limited. The template includes a no-op shim and jest mocks so web builds and tests do not break on the import, but you should not expect full Sentry capture in the browser. If web is a first-class target for your app, add a web-specific Sentry browser SDK alongside the native wiring rather than relying on @sentry/react-native to cover both.
GET IT BUILT INTO YOUR APP