BugMojoBugMojoBugMojo
FeaturesPricingBlogGuidesAbout
Log inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Get started
  • Log in

Resources

  • Blog
  • Guides
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Privacy
  • Sitemap
  • Engineering
  • Playbooks
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Bug tracking by framework
  4. Remix (now React Router v7)
Bug tracking by framework

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.

6 min read·JavaScript
Isometric line-art browser window split along its server/client seam, a lime data packet crossing the SSR boundary toward an IDE node, with a peeling ErrorBoundary card and a network-HAR waterfall beneath

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.

Production ErrorBoundary sanitizes the stack trace away

When a loader, action, or component throws on the server in production, Remix replaces the message with "Unexpected Server Error" and sets the stack to undefined before it reaches the client. The user-visible UI shows almost nothing about what failed. The real detail only exists in your server logs and in the network response, so capture the failing request's status code and body — not the boundary text.

ErrorBoundary scopes to one route and bubbles when absent

If the throwing route exports its own ErrorBoundary, only that nested section renders the error and the rest of the app renders normally. If it does not, the error bubbles up to the root boundary and blanks the whole page. The same underlying throw produces two very different screens depending on which route caught it, so you need the replay to see which boundary actually rendered.

Action errors render the boundary on the client only

A loader error is server-rendered and arrives in the initial HTML; an action error happens after a client-side form submit, so its boundary renders client-side. If the component reads the wrong hook, returns data instead of throwing, or the action resolves 200 with an error payload, nothing renders and the form looks like it silently succeeded. Read useRouteError() inside the boundary and branch with isRouteErrorResponse().

defer collapses if you await the promise

Remix streams by inserting script tags into the DOM as deferred promises resolve, which requires you to return the unawaited Promise. Accidentally awaiting it before passing it to defer turns the stream back into one blocking request — slower, with no error. The symptom (everything loads at once) is invisible in a still image and only legible in a HAR that shows whether streamed chunks arrived incrementally.

A rejected Await child spins the Suspense fallback forever

Inside a defer boundary, if the streamed promise never resolves or rejects within its Await child, the Suspense fallback stays on screen indefinitely instead of showing an error. From a screenshot an endless skeleton looks identical to a slow network. The network HAR is decisive: it shows whether the streamed promise chunk arrived and stalled, or never came at all.

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.

tsxtsx
// 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.

tsxtsx
// 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.

tsts
// 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.

FeatureBugMojoSentry / server logs
AI agent reads the captured bug in the IDE (MCP)Yes — Claude Code / Cursor triage it as an assigneeNo MCP agent surface
DOM session replay of the exact sessionYes — rrwebPartial (Sentry Session Replay); none from logs
Full network HAR with the thrown Response statusYes, with PII redactionServer-side error only
Shows which nested ErrorBoundary caught the throwYes — visible in the replayNo
Always-on production error monitoring at scaleNo — capture is on-demand, per bugYes — Sentry's core strength
Aggregates Remix server stack traces across all sessionsNoYes — Sentry is better here
Console + network (HAR) capture✓Partial
Zero-setup Quick CaptureNo project, no SDKAccount / SDK required
The BugMojo column is highlighted. The closing rows are BugMojo’s core wedge: rrweb session replay, MCP for AI agents, console + network capture, and zero-setup Quick Capture.
Capture your next bug in 15 seconds

BugMojo records the DOM, console, and network — then ships a one-click ticket with the full replay attached. No SDK, no setup.

Try BugMojo free

Frequently asked questions

Frequently asked questions

Sources

  1. 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)
  2. 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)
  3. Remix Error Handling guide — ErrorBoundary scopes the error to one route, uncaught errors bubble to root, production sanitizes server errors — Remix official docs (2024)
  4. Remix ErrorBoundary route module — useRouteError() and isRouteErrorResponse() are the documented error-access APIs — Remix official docs (2024)
  5. Remix Streaming guide — defer must return the unawaited Promise; streaming inserts script tags as deferred promises resolve — Remix official docs (2024)
  6. State of JS 2024, Meta-Frameworks — 720 respondents use Remix at work vs 5,147 for Next.js — State of JS 2024 (Devographics) (2024)
Share:

More frameworks

Pick another — each guide has its own gotchas, comparison, and fixes.

React 19
JavaScript
Next.js 15
JavaScript
SvelteKit 2 (Svelte 5)
JavaScript
Vue 3.5
JavaScript
WordPress
PHP / CMS
Angular 19
TypeScript

On this page

  • What Remix teams ship with BugMojo
  • Remix gotchas
  • Common Remix bugs
  • BugMojo vs alternatives