#15486·react-router

SSR: pipeThrough(new TextEncoderStream()) crashes Chrome Mobile iOS / WKWebView apps (RangeError: Maximum call stack size exceeded)

Author: TBeerenCreated Sep 10, 2026Updated Sep 14, 2026
Labelspkg:react-routerfeat:streaming

Environment

  • React Router: 8.x (SSR, <Scripts /> component)
  • Affected browsers: Chrome Mobile iOS, Google Search app, and any other WKWebView-based browser on iOS
  • Unaffected: Safari iOS, all desktop browsers, Android Chrome

Symptom

Every SSR page load throws:

RangeError: Maximum call stack size exceeded
    at Tk (chrome-extension://…)
    at Rk (chrome-extension://…)
    at Tk (chrome-extension://…)
    at Rk (chrome-extension://…)
    …

Tk/Rk are minified symbols. They are not WebKit JS builtins — WebKit uses full English names like readableStreamPipeTo. They appear to come from Chrome's injected WKUserScript running in page context.

Root cause

React Router 8's <Scripts /> component emits this inline script on every SSR page load:

window.__reactRouterContext.stream = new ReadableStream({
  start(controller) {
    window.__reactRouterContext.streamController = controller;
  }
}).pipeThrough(new TextEncoderStream());

Chrome Mobile iOS (and other WKWebView-based browsers) inject minified JavaScript into every page via WKUserScripts at documentStart. These scripts appear to hook into one or both of ReadableStream.prototype.pipeThrough and TextEncoderStream, causing TkRk mutual recursion that overflows WKWebView's restricted JS call stack.

We cannot determine with certainty whether pipeThrough or TextEncoderStream is the specific trigger, because Chrome Mobile iOS does not allow Web Inspector connections — we cannot inspect Chrome's injected WKUserScript source or observe pipeThrough.toString() in Chrome's context. What we can confirm:

  • The crash occurs in the React Router SSR inline script (it is the first script executed on page load)
  • Only WKWebView-based browsers are affected; Safari is not
  • Removing both pipeThrough and TextEncoderStream from the inline script eliminates the crash entirely

The relevant line is the contextScript in lib/dom/ssr/components.js inside Scripts → useMemo → initialScripts.

Impact

This is not a browser crash — the WKWebView process survives. But the consequence is severe:

  1. Inline context script throws RangeError → aborts
  2. window.__reactRouterContext.stream is never assigned
  3. window.__reactRouterContext.streamController is undefined
  4. Subsequent inline <script> tags from StreamTransfer call streamController.enqueue(...)TypeError
  5. decodeViaTurboStream waits for data that never arrives
  6. App does not hydrate — user sees a broken, non-interactive page

This affects all React Router 8 SSR apps using <Scripts /> on Chrome iOS, regardless of whether they use defer() or streaming loaders — the pipeThrough / TextEncoderStream call is unconditional.

Proposed fix

Replace pipeThrough(new TextEncoderStream()) with a manual TextEncoder wrapper inside the ReadableStream start handler. This eliminates both pipeThrough and TextEncoderStream from the inline script. Output is functionally identical (ReadableStream<Uint8Array>, same enqueue/close/error API for decodeViaTurboStream):

// Before
window.__reactRouterContext.stream = new ReadableStream({
  start(controller) {
    window.__reactRouterContext.streamController = controller;
  }
}).pipeThrough(new TextEncoderStream());

// After
window.__reactRouterContext.stream = new ReadableStream({
  start(controller) {
    var enc = new TextEncoder();
    window.__reactRouterContext.streamController = {
      enqueue: function(s) { controller.enqueue(enc.encode(s)); },
      close:   function()  { controller.close(); },
      error:   function(e) { controller.error(e); }
    };
  }
});

We have verified at scale (~144M affected page loads/month across our RR8 SSR fleet) that this eliminates the crash, and that decodeViaTurboStream operates correctly with the patched stream.

Workaround

Apply as a pnpm patch (patchedDependencies) or equivalent until a fix lands in a React Router release.