T
TanStackโ€ข2mo ago
quickest-silver

TanStack Start and Emotion with SSR and streaming

I found this previous question and a github post about this topic: https://discord.com/channels/719702312431386674/1345163703267627088/1345163703267627088 https://github.com/TanStack/router/issues/2867 I have verified that the approach referenced in both does work for SSR, but when I attempt to use streaming, I hit the hydration mismatch error again. It appears to be a race condition between the streamed document request completing and some component that was initially server rendered and is using useSuspenseQuery. If the component unsuspends before the streaming completes, that seems to be when the hydration mismatch occurs. But I'm also not sure I'm following it correctly ๐Ÿ™‚ I'm trying to follow this pattern (https://tanstack.com/router/latest/docs/integrations/query) to automate SSR dehydration/hydration and streaming between TanStack Router and TanStack Query. I've got a route that starts prefetching data in the route loader without awaiting it and then the component uses useSuspenseQuery to stream those queries as a part of the doc request: https://tanstack.com/router/latest/docs/integrations/query If I await the prefetches (which I think effectively opts out of the streaming of those queries), then there's no hydration mismatch error. This may also just be because it forces a different timing which happens to avoid the timing issue with Emotion, I'm not sure. But if I don't await them and try to stream the queries, I regularly hit the hydration mismatch. I don't have a minimal repro setup yet, but can work on one if that would be helpful. I'm mostly wondering if I'm barking up the wrong tree or if there's an example that already demonstrates TanStack Start + Emotion with SSR streaming. Versions: @tanstack/react-start: 1.132.51 @tanstack/react-query: 5.90.2 react: 19.2.0 nitro: nitro-nightly@3.0.1-20251015-114932-a0f52cc9 vite: 7.1.9
GitHub
Document how to use emotion/@mui/material/@mui/joy with Start...
TipFind an working example here. Which project does this relate to? Start Describe the bug SSR with @mui/material/@mui/joy worked flawlessly without any special setup for @tanstack/start <= 1.81...
TanStack Query Integration | TanStack Router Docs
[!IMPORTANT] This integration automates SSR dehydration/hydration and streaming between TanStack Router and TanStack Query. If you haven't read the standard guide, start there. What you get Automatic...
9 Replies
extended-salmon
extended-salmonโ€ข2mo ago
@KiwiKilian
quickest-silver
quickest-silverOPโ€ข2mo ago
I was able to update the existing start + MUI example in the router repo with a repro for the issue I'm seeing: https://github.com/TanStack/router/pull/5490 It's very possible I'm just not setting this pattern up correctly, but hopefully this should at least make it easier to see the issue
GitHub
Repro: Hydration mismatch error with TanStack Start + streaming + M...
Issue I'm trying to follow this pattern (https://tanstack.com/router/latest/docs/integrations/query) to automate SSR dehydration/hydration and streaming between TanStack Router and TanStack...
quickest-silver
quickest-silverOPโ€ข2mo ago
We've been experimenting with a similar approach in react-router framework mode and we had to reveal the server entry point and use the @emotion/server library to customize the stream handler such that it extracts the emotion styles and then injects them as style tags in the <head>, so maybe something similar is required here. I was hoping based on some of the other examples that I've seen like this start + MUI example that a simpler option might work, but maybe that's not the case ^ I just looked closer at that react-router custom server entrypoint, and it looks like we have to buffer the entire HTML response in order to use those @emotion/server APIs to extract the critical chunks and insert them as styles in the <head>, which means we aren't actually streaming the response. So I started playing around with the other options in @emotion/server like renderStylesToNodeStream but ultimately determined that shouldn't be necessary, it should just work with SSR as long as it's wrapped in a <CacheProvider> correctly. The problem seems to be with the streaming + suspense combination specifically for Emotion (or other CSS-in-JS runtime solutions). The server renders the shell, the suspense fallback, and then later renders the actual content, and Emotion inserts styles for both. But by the time the client hydrates, the query has already completed, so it only tries to render the actual content and not the fallback. Does that sound right? And if so, is there anything we can do with the streaming/query setup to work around this? I should also mention that part of the problem here that causes the hydration mismatch is that streaming (at least as far as I can tell) necessitates having Emotion inject the styles inline with the components. If you do a full server render, then you can process that output, extract the styles and inject them as style tags in the <head>, but with streaming that just isn't possible.
fair-rose
fair-roseโ€ข2mo ago
I checked the example and got the error as well. I was surprised since I also use MUI+Streaming+Start+Query and have not consistently faced this issue so far. I think the important point about your example is that the Typography component behind Suspense has a unique style applied, which causes the generation of the extra <style> tag . If you use the same styling for the suspended component and the fallback, there's no error.
wise-white
wise-whiteโ€ข2mo ago
I think we aren't using streaming anywhere yet in our projects. Did you try this change? https://github.com/TanStack/router/issues/2867#issuecomment-3061088836
quickest-silver
quickest-silverOPโ€ข4w ago
Sorry for the delayed follow-up, I just got back around to working on this. @KiwiKilian I had tried that insertion point meta tag, but that alone didn't fix the issue. @Kuoun I see what you're saying, although I think having unique styling between the Suspense content and its fallback should work. Needing to keep those consistent seems untenable. I eventually found this approach to the same problem: https://github.com/emotion-js/emotion/issues/2800#issuecomment-2644448182 - which helped me get to what I think is now a working solution for Emotion + streaming. The key seems to be differentiating between the shell and suspense boundaries. I wrote up some more about the solution I ended up with here: https://gist.github.com/evanweible-wf/6787e1d62e1aa71623fffdf8d5a5a1fe Fortunately, the fundamental problem seems to be limited to Emotion + streaming + Suspense and has nothing to do with TanStack Start. In fact, the custom server entrypoint already supported by TanStack Start makes it easy to wrap the defaultStreamHandler with a TransformStream that strips out the problematic inline styles in order to get this working without a hydration mismatch.
Gist
Emotion + Streaming SSR + Suspense.md
GitHub Gist: instantly share code, notes, and snippets.
GitHub
How to use emotion with renderToPipeableStream ยท Issue #2800 ยท em...
I&#39;m looking at how to implement SSR emotion with react 18. React18 made renderToPipeableStream method the new recommended way and deprecated the renderToNodeStream method. The documentation onl...
extended-salmon
extended-salmonโ€ข4w ago
@Evan Weible can you share a complete project for future readers?
wise-white
wise-whiteโ€ข4w ago
Awesome write-up, thanks! TBH I'm hoping to be able to ditch Emotion at some point, it requires so many quirks to make it work ๐Ÿ™ˆ.
quickest-silver
quickest-silverOPโ€ข4w ago
@Manuel Schiller yes will do! I'll update the PR I had opened with this original thread to include a streaming example. You all can choose whether it's worth merging that in or just leaving here for reference @KiwiKilian yeah, it certainly does ๐Ÿ˜ญ I'd probably use something else for new projects, but we're stuck with it at work for the foreseeable future ๐Ÿ˜„

Did you find this page helpful?