remix-run/react-router · error

() call aborted without an `AbortSignal.reason

Error message

${method}() call aborted without an `AbortSignal.reason`: ${request.method} ${request.url}

What it means

createStaticHandler's query()/queryRoute() aborts by re-throwing the AbortSignal's reason so callers can tell intentional cancellation apart from crashes. If the signal was aborted with no reason (a bare controller.abort()), throwStaticHandlerAbortedError (lib/router/router.ts:5092) throws this error because the framework cannot classify the abort.

Solutions

  1. Always abort with a reason: controller.abort(new Error('timeout')) or new DOMException('timeout', 'TimeoutError')
  2. Check for double-abort: guard with if (signal.aborted) return before calling abort() again
  3. Ensure only one owner aborts a shared signal
  4. Catch the re-thrown reason in your handler and map known reasons to proper responses

Example fix

// before
setTimeout(() => controller.abort(), 5000)
// after
setTimeout(() => controller.abort(new Error('SSR request timeout')), 5000)
Defensive patterns

Strategy: validation

Validate before calling

// Single-owner abort helper that always attaches a reason
function abortWithReason(ctrl: AbortController, reason: unknown) {
  if (ctrl.signal.aborted) return;
  ctrl.abort(reason instanceof Error ? reason : new Error(String(reason)));
}
setTimeout(() => abortWithReason(controller, 'SSR timeout'), 5000);

Try / catch

try {
  const ctx = await staticHandler.query(request);
} catch (e) {
  if (request.signal.aborted) {
    // distinguish intentional aborts: reason is re-thrown by the handler
    return new Response('Aborted: ' + String(request.signal.reason), { status: 499 });
  }
  throw e;
}

Prevention

When it happens

Trigger: SSR request handling with a bare timeout abort: setTimeout(() => controller.abort(), 5000); calling abort() twice where the second call has no reason; a shared signal aborted by another consumer (e.g., a competitor fetch wins and aborts the common signal); environments whose abort implementation does not populate signal.reason.

Common situations: Node/edge SSR handlers adding request timeouts; React streaming SSR where an earlier render pass is aborted; Promise.race patterns that abort a shared controller; older fetch polyfills in Node that do not set signal.reason.

Related errors


AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18). Data as JSON: /api/errors/559a2270db71da2a. Report an issue: GitHub.

Appendix: source

Thrown at packages/react-router/lib/router/router.ts:5079

  return {
    ...handlerContext,
    statusCode: isRouteErrorResponse(error) ? error.status : 500,
    errors: {
      [errorBoundaryId]: error,
    },
  };
}

function throwStaticHandlerAbortedError(
  request: Request,
  isRouteRequest: boolean,
) {
  if (request.signal.reason !== undefined) {
    throw request.signal.reason;
  }

  let method = isRouteRequest ? "queryRoute" : "query";
  throw new Error(
    `${method}() call aborted without an \`AbortSignal.reason\`: ${request.method} ${request.url}`,
  );
}

function isSubmissionNavigation(
  opts: BaseNavigateOrFetchOptions,
): opts is SubmissionNavigateOptions {
  return (
    opts != null &&
    (("formData" in opts && opts.formData != null) ||
      ("body" in opts && opts.body !== undefined))
  );
}

function defaultNormalizePath(request: Request): Path {
  let url = new URL(request.url);
  return {
    pathname: url.pathname,

View on GitHub (pinned to 7aea711dd1)