Skip to content

Report browser errors to Sentry - #469

Open
str1fe wants to merge 3 commits into
mainfrom
t3code/frontend-sentry
Open

str1fe wants to merge 3 commits into
mainfrom
t3code/frontend-sentry

Conversation

@str1fe

@str1fe str1fe commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Why

Sentry only covered the Rails side. The Plan crash after switching language (#468) threw hundreds of errors in the browser, and nobody would have known without checking the console.

What

  • @sentry/browser is initialised first in application.js (app/javascript/src/sentry.js). It catches uncaught errors and unhandled rejections, including errors thrown from requestAnimationFrame callbacks, where Elm renders.
  • SentryHelper#sentry_meta_tags renders the configuration in base and error layouts, but only where the Ruby SDK is enabled (production and staging), so development and test send nothing.
  • The DSN comes from SENTRY_FRONTEND_DSN, with no fallback to SENTRY_KEY: it's rendered into every page, so the backend DSN must never end up there. It points to the Sentry project stemplin-frontend (id 32), which is limited to *.stemplin.com origins and rate limited. It has the same alert rules as stemplin and the browser-extension, legacy-browser and web-crawler filters turned on. The variable is already set on Dokku.
  • Each event is tagged with environment, release, user id and organization id. Only ids are sent, no names or emails.
  • An error is only reported if the line that threw it is in one of our own bundles under /assets/ (application-*.js, plan-*.js). That drops browser extensions, inline third-party snippets (gtag, Clarity, PostHog, AOS) and errors without a stack. Sentry's own timer wrappers don't count, because only the innermost frame is checked.
  • Network failures and aborted requests (Failed to fetch, NetworkError, Load failed, …aborted…) are ignored. So are Sentry's default filters (ResizeObserver, Script error.).
  • Stimulus controller errors are covered too: Stimulus forwards them to window.onerror.
  • A render loop can throw on every frame, so reports are capped at 20 per page load.
  • config.release is set from Dokku's GIT_REV so backend and frontend events share a release.

Verified

  • Helper tests: nothing is rendered where Sentry is disabled, the backend DSN is never rendered when SENTRY_FRONTEND_DSN is missing, and the full set of tags is rendered when it's set.
  • A throwaway system test pointed the SDK at a local fake Sentry endpoint. Errors from our bundle arrived with user, organization and environment: a thrown error in a timer, and an error inside a Stimulus controller's connect. Inline errors were all dropped: timer, requestAnimationFrame, event listener, promise rejection, and failed fetch.
  • Full suite passes: 449 unit, 99 system, rubocop clean.

After merging

  • Wait for the deploy run to go green before merging anything else. A second merge during a deploy hits Dokku's deploy lock and silently skips that deploy, which is what happened to Fix Plan breaking after switching language #468.
  • Production should then serve <meta name="sentry-dsn">, and the first real browser error should show up in stemplin-frontend.

Not included

  • Source map upload. application.js isn't minified, so stack traces are readable. plan.js is minified by the Elm build, and uploading maps would need sentry-cli and an auth token in the Docker build.

🤖 Generated with Claude Code

str1fe and others added 3 commits September 29, 2026 16:21
Only the Rails side reported to Sentry, so frontend failures such as the
Elm plan app crashing after a language switch never showed up anywhere.

Load @sentry/browser from the application bundle and configure it from
meta tags that are rendered only where the Ruby SDK is enabled. Events
carry the environment, release, user id and organization id, are limited
to our own scripts, and are capped per page load so a render loop cannot
flood the project.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
allowUrls lets through errors without a usable filename and anything
thrown from inline third-party snippets on our own origin. Require the
frame that threw to be one of our /assets/ bundles instead, and ignore
network failures and aborted requests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The DSN is rendered into every page. Only use the frontend project's,
which is limited to our origins and rate limited.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant