Bug tracking for Remix — session replay, console + HAR (2026)
Debug Remix loader, action, and ErrorBoundary failures that straddle the SSR boundary. Capture rrweb replay, console, and network HAR — readable by AI agents over MCP.
What Remix teams ship with BugMojo
A Remix bug rarely lives on one side of the wire. The loader runs on the server, the action's revalidation runs back on the client, and the ErrorBoundary renders on whichever side actually threw — so the failing data shape and the rendered fallback end up on different halves of the request. That split is why a screenshot is nearly useless here: it shows the client-side fallback while the real cause sits in a server loader you cannot see from the DOM.
Remix also hides its own errors on purpose. In production the framework sanitizes any server throw before it reaches the browser: the message becomes "Unexpected Server Error" and the stack is undefined, so no internals leak. The detail you need survives only in the network response and in what the user did to trigger the throw. To debug a Remix loader/action failure you want three artifacts together — the rrweb DOM replay, the console (where hydration warnings name the mismatched node), and the full network HAR (where the thrown Response's real status code lives). BugMojo captures all three from one on-demand session.
Remix gotchas
Framework-specific failure modes engineers actually ship through. Each is hard to spot in a screenshot and obvious in a session replay.
Common Remix bugs
Real Remix bug patterns — the symptom you will see in a report and the fix that actually works.
Hydration mismatch from a non-deterministic value
Symptom: Console warning that the server HTML did not match the client render; text flickers or a node swaps on first paint. Common triggers are a Date, locale-dependent formatting, a random id, or a browser extension mutating the DOM.
Fix: Remix's own guidance is that this is not a Remix bug but how React SSR works. The durable fix is to render the value only after hydration behind an isHydrated flag (the useHydrated hook from remix-utils) rather than reaching for suppressHydrationWarning, which only silences one node and hides the symptom. A captured console plus DOM replay shows the exact node that flickered and the warning that named it.
// the value differs between server and client render
// fix: gate it behind a hydration flag
import { useHydrated } from 'remix-utils/use-hydrated';
function Now() {
const hydrated = useHydrated();
return <span>{hydrated ? new Date().toLocaleTimeString() : null}</span>;
}Action throws but the form looks like it saved
Symptom: User submits a form, the page shows no error, but nothing was persisted. The validation message your ErrorBoundary should display never appears.
Fix: An action that returns an error object (or resolves 200 with an error payload) instead of throwing a Response will not trigger the ErrorBoundary. Either throw a Response with the right status so isRouteErrorResponse() can branch on it, or surface the returned error in the component via useActionData. The captured HAR shows the POST to the action and its real status code, so you can tell whether the action threw, returned, or quietly resolved.
// inside the route's ErrorBoundary
import { useRouteError, isRouteErrorResponse } from '@remix-run/react';
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <p>{error.status} {error.statusText}</p>;
}
return <p>Something went wrong</p>;
}Deferred data streams everything at once or never finishes
Symptom: Either the page blocks until all data is ready (no streaming at all), or a skeleton spins forever for a deferred section.
Fix: Loading everything at once usually means you awaited the promise before passing it to defer; return the unawaited Promise so Remix can stream it by injecting script tags as it resolves. A skeleton that spins forever means the streamed promise never resolved or rejected inside its Await boundary. The network HAR distinguishes the two at a glance: it shows whether the streamed chunk arrived and which one stalled.
// loader
import { defer } from '@remix-run/node';
// pass the UNawaited promise, do not await it here
export function loader() {
return defer({ slow: getSlowData() });
}BugMojo vs alternatives
The honest comparison — where BugMojo wins, and where another tool might serve you better.
| Feature | BugMojo | Sentry / server logs |
|---|---|---|
| AI agent reads the captured bug in the IDE (MCP) | Yes — Claude Code / Cursor triage it as an assignee | No MCP agent surface |
| DOM session replay of the exact session | Yes — rrweb | Partial (Sentry Session Replay); none from logs |
| Full network HAR with the thrown Response status | Yes, with PII redaction | Server-side error only |
| Shows which nested ErrorBoundary caught the throw | Yes — visible in the replay | No |
| Always-on production error monitoring at scale | No — capture is on-demand, per bug | Yes — Sentry's core strength |
| Aggregates Remix server stack traces across all sessions | No | Yes — Sentry is better here |
| Console + network (HAR) capture | ✓ | Partial |
| Zero-setup Quick Capture | No project, no SDK | Account / SDK required |
BugMojo records the DOM, console, and network — then ships a one-click ticket with the full replay attached. No SDK, no setup.
Try BugMojo freeFrequently asked questions
Frequently asked questions
Sources
- Merging Remix and React Router — Remix v3 becomes React Router v7; the Remix packages "take a little nap" — Remix (Shopify) official blog (2024-05-15)
- React Router v7 stable release — brings Remix's SSR, loaders/actions, type safety, and HMR into React Router framework mode — Remix (Shopify) official blog (2024-11-22)
- Remix Error Handling guide — ErrorBoundary scopes the error to one route, uncaught errors bubble to root, production sanitizes server errors — Remix official docs (2024)
- Remix ErrorBoundary route module — useRouteError() and isRouteErrorResponse() are the documented error-access APIs — Remix official docs (2024)
- Remix Streaming guide — defer must return the unawaited Promise; streaming inserts script tags as deferred promises resolve — Remix official docs (2024)
- State of JS 2024, Meta-Frameworks — 720 respondents use Remix at work vs 5,147 for Next.js — State of JS 2024 (Devographics) (2024)

