Skip to content

SSR Streaming corrupts multi-byte UTF-8 characters (Hebrew/Arabic) causing hydration mismatch #6223

@LielAmar

Description

@LielAmar

Which project does this relate to?

Start

Describe the bug

For about 3 months, I've been dealing with Hydration mismatches in Tanstack Start. I was always able to fix the issues temporarily, and there were never clear signs of why the issues happened in the first place. Sometimes it was an import, that when deleted, fixed the Hydration mismatches, sometimes it was just changing some of the text etc. The Hydration errors in the console were never informative, besides showing corrupted characters in Hebrew.

After trying to investigate this error for the 6th time in these past few months, I used an LLM to go through everything and figured there might be an issue with SSR streaming with Tanstack Start, which causes these hydration mismatches when rendering multi-byte UTF-8 characters (Like in Hebrew or Arabic). The reasoning behind this claim is that the streaming SSR splits HTML output into chunks, and multi-byte characters can be split across chunk boundaries, resulting in the said corrupted characters ( replacement character) and hydration failures.

Your Example Website or App

www.kedai.co.il

Steps to Reproduce the Bug or Issue

The issue is not deterministic. I am unable to give code that would reproduce this issue because to this day, I have no idea what caused any of the 6 hydration issues I've dealt with. However, I'm able to tell it's not deterministic because of the following:

I noticed a new hydration error that started happening at some point in the past few weeks. Once again, this hydration error only happened with the Hebrew translation of my website (never happened for English), and the hydration error in the console once again showed corrupt characters. Going back through my commit history, I found out that the hydration error appeared between two commits. The only changes between them were:

diff --git a/src/routes/{-$locale}/-components/navbar.tsx
-            <SheetTrigger className="col-start-3 flex justify-end lg:hidden" asChild>
+            <SheetTrigger className="col-start-3 flex lg:hidden" asChild>

diff --git a/src/styles/index.css
-  --sheet-z: 2147480000;
+  --sheet-z: 3000;

Neither change affects any Hebrew text or rendering logic, yet they triggered hydration errors, probably by shifting the byte positions in the SSR output.

The reason it took me a few weeks to even notice this hydration error is because I used React.lazy() to load some of the SSR components I was using. This caused these components to render on the client, which avoided the SSR streaming.

Expected behavior

Not getting an hydration mismatch. However, I do get an error. You can see an example error below.

Uncaught Error: Hydration failed because the server rendered text didn't match the client.

Server rendered: מאחו��י (corrupted - contains U+FFFD replacement character)
Client expected: מאחורי (correct Hebrew text meaning "behind")

The character "ר" (resh, UTF-8 bytes: D7 A8) was split across streaming chunks.

Screenshots or Videos

No response

Platform

  • @tanstack/react-start: ^1.132.0 (also reproduced on ^1.140.0)
  • @tanstack/react-router: ^1.132.0
  • React: ^19.2.0
  • Vite: ^7.1.7
  • Runtime: Cloudflare Workers
  • OS: Windows 11

Additional context

I'd love to give more information if needed :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions