T
TanStack2mo ago
following-aqua

Why is there a delay between when the __root.tsx layout loads and the <Outlet />?

I have deployed my project on Netilify. When you refresh the page you can see the layout loads instantly but after a split second, then the outlet loads. If you look at dev tools and refresh the page, you can see the main tag being injected in. This happens on any page. How do I fix this? Thanks. Here are the links: https://sovereign-atelier.netlify.app/ https://github.com/ikraamdaanis/gallery
71 Replies
fascinating-indigo
fascinating-indigo2mo ago
Do you have a beforeLoad with any asynchronous data?
following-aqua
following-aquaOP2mo ago
No, all the data is in the repo.
frozen-sapphire
frozen-sapphire2mo ago
cannot access the repo
following-aqua
following-aquaOP2mo ago
oops, try now
continuing-cyan
continuing-cyan2mo ago
fwiw, this does happen to me too on my own project. I use beforeLoad but I've tested without it and same issue. It's just the root layout. route.tsx ones dont have this issue. Wasn't a problem pre-RC
frozen-sapphire
frozen-sapphire2mo ago
reproducer?
continuing-cyan
continuing-cyan2mo ago
Would love to but yea it's a massive repo. Just wanted to mention it in case it was helpful to like realize its not a singular issue with OP. It's not a big deal for me personally, but noticed it for sure. I will see if i can make a small repro, I'll lyk
frozen-sapphire
frozen-sapphire2mo ago
GitHub
Tanstack Start base div hidden on JS disabled · Issue #5383 · Tan...
Which project does this relate to? Start Describe the bug At the latest version of Tanstack Start if you disable JS in browser the page is empty because the div that contains all content has param ...
continuing-cyan
continuing-cyan2mo ago
Checking. It shows as <template> first (and doesn't show on the page) and then changes to a div. Sorry it's an image but this happens so fast, I had to screen record and then screenshot from the video lol
No description
continuing-cyan
continuing-cyan2mo ago
Yea, and then in the SSR response, the content is actually in a <div hidden id="S:0"> at the bottom. I'm going to try to make a fresh small repro with this issue, i'll lyk
frozen-sapphire
frozen-sapphire2mo ago
you should be able to inspect the SSR response html (view source instead of inspect document )
following-aqua
following-aquaOP2mo ago
I bothers my OCD a lot lol. Is there a fix?
continuing-cyan
continuing-cyan2mo ago
Yea it's also a potential SEO concern. I'm checking and doing some debugging, will lyk Still digging, at this point not sure if it's a TS or React issue, but using a very simple ArtCard component version of what ikraam shared
import { Link } from "@tanstack/react-router";
import type { ArtPiece } from "data/art-pieces";
import { useState } from "react";
import { cn } from "utils/cn";

export function ArtCard({ artPiece }: { artPiece: ArtPiece }) {
const [isHovered, setIsHovered] = useState(false);

return (
<article
className="group relative"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Link
to="/art/$id"
params={{ id: artPiece.id }}
aria-label={`View details for ${artPiece.title} by ${artPiece.artist}`}
className="block"
>
<div className="relative overflow-hidden bg-neutral-100">
<div className="relative h-full w-full">
<img
src={artPiece.imageUrl}
alt={`${artPiece.title}oooooOoooooo`}
className={cn(
"block h-full w-full object-cover transition-opacity duration-300"
)}
/>
</div>
</div>
</Link>
</article>
);
}
import { Link } from "@tanstack/react-router";
import type { ArtPiece } from "data/art-pieces";
import { useState } from "react";
import { cn } from "utils/cn";

export function ArtCard({ artPiece }: { artPiece: ArtPiece }) {
const [isHovered, setIsHovered] = useState(false);

return (
<article
className="group relative"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Link
to="/art/$id"
params={{ id: artPiece.id }}
aria-label={`View details for ${artPiece.title} by ${artPiece.artist}`}
className="block"
>
<div className="relative overflow-hidden bg-neutral-100">
<div className="relative h-full w-full">
<img
src={artPiece.imageUrl}
alt={`${artPiece.title}oooooOoooooo`}
className={cn(
"block h-full w-full object-cover transition-opacity duration-300"
)}
/>
</div>
</div>
</Link>
</article>
);
}
adding one more single o letter in that image alt tag will make the page do that whole streaming template thing, if I keep the same number of os, works fine. I don't know if that's due to byte size, or just somewhat of a race condition in terms of what finishes before the other (layout vs. body). but yea, digging through, just sharing findings
following-aqua
following-aquaOP2mo ago
very strange, I'll change the alt and push that didn't change anything. That template element is from React suspense right?
continuing-cyan
continuing-cyan2mo ago
No no, not saying this is a fix. I'm just reducing things from your code to identify issues. Still have no clue what's happening exactly.
following-aqua
following-aquaOP2mo ago
Do you think it’s netlify causing it?
continuing-cyan
continuing-cyan2mo ago
No, it's some React streaming thing. Honestly might not even be a TanStack issue, but yea. Will need to take a look -- will see how much more time I have today, and obv Manuel can help too if he has time.
following-aqua
following-aquaOP2mo ago
Thanks I think the issue is the fact I had other components in the root layout instead of just the Outlet. I created a new file with a pathless layout for the header and footer and the outlet content loads instantly even though you can still see the <template/> tag very strange
fascinating-indigo
fascinating-indigo2mo ago
What happens if you change component: RootComponent to shellComponent: RootComponent ? Ah yes, shellComponent must house your <html> tag, pretty sure
fascinating-indigo
fascinating-indigo2mo ago
Selective Server-Side Rendering (SSR) | TanStack Start Solid Docs
What is Selective SSR? In TanStack Start, routes matching the initial request are rendered on the server by default. This means beforeLoad and loader are executed on the server, followed by rendering...
continuing-cyan
continuing-cyan2mo ago
Believe that does the opposite of what we're trying to do -- might be wrong.
frozen-sapphire
frozen-sapphire2mo ago
so this only occurs with react 19.2 not with 19.1
frozen-sapphire
frozen-sapphire2mo ago
fascinating-indigo
fascinating-indigo2mo ago
I'm not suggesting to disable SSR but that explains what shellComponent does, can't find it anywhere else in the docs and I noticed his root route is not using shellComponent https://github.com/TanStack/router/blob/b5c96d42cf519eb2a0f60379c3dc65b1e68b77ac/examples/react/start-basic/src/routes/__root.tsx#L62 vs https://github.com/ikraamdaanis/gallery/blob/f517650eb075839d1c1196188a5f2f3029197b4d/app/routes/__root.tsx#L25 Wouldn't batched suspense boundaries actually prevent flicker? Since the whole point of it is to render everything at the same time with I think a 2.5 second threshold if I'm not mistaken
frozen-sapphire
frozen-sapphire2mo ago
not sure yet. will talk with react team
following-aqua
following-aquaOP2mo ago
I worked around this issue by moving these components to another layout file, I think having these extra components in the root layout was causing the delayed load time for the Outlet:
function RootDocument({ children }: { children: ReactNode }) {
return (
<html lang="en" className="overscroll-none antialiased">
<head>
<HeadContent />
</head>
<body>
<Navigation />
{children}
<AcquirePieces />
<Footer />
<Toaster position="top-center" richColors closeButton />
<Scripts />
</body>
</html>
);
}
function RootDocument({ children }: { children: ReactNode }) {
return (
<html lang="en" className="overscroll-none antialiased">
<head>
<HeadContent />
</head>
<body>
<Navigation />
{children}
<AcquirePieces />
<Footer />
<Toaster position="top-center" richColors closeButton />
<Scripts />
</body>
</html>
);
}
Now I changed it to this:
function RootDocument({ children }: { children: ReactNode }) {
return (
<html lang="en" className="overscroll-none antialiased">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
);
}
function RootDocument({ children }: { children: ReactNode }) {
return (
<html lang="en" className="overscroll-none antialiased">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
);
}
fascinating-indigo
fascinating-indigo2mo ago
Can you try the default setup of setting shellComponent to RootDocument with no {children} props, do you still encounter this issue? I cloned your repo and checked out the commit from yesterday and wasn't able to reproduce your flicker locally
following-aqua
following-aquaOP2mo ago
Flicker happens locally on dev if you spam refresh the browser you can sometimes see it. We you deploy it you'lll see it more often. Here's the preview using the shellComponent and ssr: false https://ikraam-test--sovereign-atelier.netlify.app/ following this page: https://tanstack.com/start/latest/docs/framework/react/guide/selective-ssr#how-to-disable-ssr-of-the-root-route
Selective Server-Side Rendering (SSR) | TanStack Start React Docs
What is Selective SSR? In TanStack Start, routes matching the initial request are rendered on the server by default. This means beforeLoad and loader are executed on the server, followed by rendering...
fascinating-indigo
fascinating-indigo2mo ago
Do you need SSR to be false? SSR should be true The only thing I was saying to try was to change "component" to "shellComponent", the root layout with <html> should always be shellComponent, and without SSR you will get a white screen before render
following-aqua
following-aquaOP2mo ago
If ssr is true then you get the same issue: https://deploy-preview-1--sovereign-atelier.netlify.app/
fascinating-indigo
fascinating-indigo2mo ago
In this link i'm just seeing font and styles flickering, but the layout seems correct ah wait I see now Which commit is this? Seems like Safari and Chrome are loading it differently, Safari flickers in the fonts and styles with all the layouts loaded, but Chrome has the issue where the Outlet is delayed
following-aqua
following-aquaOP2mo ago
I guess that’s another issue. this is a fresh install of tanstack start using the cli and it has the same issue: https://billsend.netlify.app/
frozen-sapphire
frozen-sapphire2mo ago
you might not have seen my message above. there is an issue with react 19.2 which either is a bug in react or we need to fix this in start. we are waiting for react maintainers to get back to us about this. as soon as I know more I will let you know
following-aqua
following-aquaOP2mo ago
sorry, didn't see. If I downgrade to 19.1 in package.json will it fix it?
frozen-sapphire
frozen-sapphire2mo ago
yes
following-aqua
following-aquaOP2mo ago
It's on 19.1 and the issue is still there: https://billsend.netlify.app/ It's when you have any other component beside the <Outlet/> in the root layout
frozen-sapphire
frozen-sapphire2mo ago
in the gallery example it is fixed for me with 19.1
following-aqua
following-aquaOP2mo ago
I changed the gallery example and moved the header/footer from the route layout
following-aqua
following-aquaOP2mo ago
GitHub
GitHub - ikraamdaanis/gallery at 2b7fbea3038fa614ee7d8d977a1feb883f...
gallery. Contribute to ikraamdaanis/gallery development by creating an account on GitHub.
frozen-sapphire
frozen-sapphire2mo ago
i specifically checked the returned SSR HTML for <div hidden id="S:0"> it exists with 19.2 but not with 19.1
following-aqua
following-aquaOP2mo ago
this commit is my work around for this issue so it doesn't have that problem with the outlet taking a split second to load it
following-aqua
following-aquaOP2mo ago
GitHub
GitHub - ikraamdaanis/gallery at ikraam/test
gallery. Contribute to ikraamdaanis/gallery development by creating an account on GitHub.
following-aqua
following-aquaOP2mo ago
this branch is on react 19.1
fascinating-indigo
fascinating-indigo2mo ago
This is only happening on prod right? Not dev ?
frozen-sapphire
frozen-sapphire2mo ago
it is not you have "react": "^19.1.0", which will install react 19.2.2 notice the ^ remove that and reinstall
following-aqua
following-aquaOP2mo ago
ah good catch yeah, I don't see the problem anymore https://ikraam-test--sovereign-atelier.netlify.app/ thanks just have an issue with the playfair font taking a split second to load in lol fixed by preloading font
dependent-tan
dependent-tan2mo ago
GitHub
[Fizz] Outline if a boundary would add too many bytes to the next c...
…pletion (#33029) Follow up to #33027. This enhances the heuristic so that we accumulate the size of the currently written boundaries. Starting from the size of the root (minus preamble) for the ...
continuing-cyan
continuing-cyan2mo ago
Yep, pretty sure that's the one. https://discord.com/channels/719702312431386674/1431549315809284146/1431678127360180489 like i mentioned here, it literally took an extra letter to trigger the suspense
dependent-tan
dependent-tan2mo ago
So once the shell hits ~12.5 KB then any Suspense boundary larger than 500 bytes triggers this behaviour And the native suspense boundary fallback is basically empty (<!--$?--> + hidden <div>) so that is expected that the client sees an empty SSR output until hydration finishes ugh
continuing-cyan
continuing-cyan2mo ago
Wonder if there's a way to opt-out of that
dependent-tan
dependent-tan2mo ago
there is, you can do this:
renderToPipeableStream(<App />, {
progressiveChunkSize: Number.POSITIVE_INFINITY, // or just a very large number
});
renderToPipeableStream(<App />, {
progressiveChunkSize: Number.POSITIVE_INFINITY, // or just a very large number
});
continuing-cyan
continuing-cyan2mo ago
Testing this out rn, let's see
dependent-tan
dependent-tan2mo ago
lmk, i'm out n about so i'd be v interested
continuing-cyan
continuing-cyan2mo ago
export const renderRouterToStream = async ({
request,
router,
responseHeaders,
children,
}: {
request: Request
router: AnyRouter
responseHeaders: Headers
children: ReactNode
}) => {
if (typeof ReactDOMServer.renderToReadableStream === 'function') {
const stream = await ReactDOMServer.renderToReadableStream(children, {
signal: request.signal,
nonce: router.options.ssr?.nonce,
progressiveChunkSize: Number.POSITIVE_INFINITY
})
export const renderRouterToStream = async ({
request,
router,
responseHeaders,
children,
}: {
request: Request
router: AnyRouter
responseHeaders: Headers
children: ReactNode
}) => {
if (typeof ReactDOMServer.renderToReadableStream === 'function') {
const stream = await ReactDOMServer.renderToReadableStream(children, {
signal: request.signal,
nonce: router.options.ssr?.nonce,
progressiveChunkSize: Number.POSITIVE_INFINITY
})
So yea, this worked in renderRouterToStream.tsx Solved the issue for me Can submit a PR for this but feels like a blanket fix? Not sure
dependent-tan
dependent-tan2mo ago
yeah i think changing this default behaviour needs more thought - we should probably have a config param or something .... i wonder what next.js has this value set to
continuing-cyan
continuing-cyan2mo ago
It's a config param or 12,800
dependent-tan
dependent-tan2mo ago
for next.js?
continuing-cyan
continuing-cyan2mo ago
Yea
dependent-tan
dependent-tan2mo ago
I can't see how I can set progressiveChunkSize in Next.js conf
dependent-tan
dependent-tan2mo ago
this is just their fork of react dom
continuing-cyan
continuing-cyan2mo ago
Ah interesting. So yea does seem to be 12,800
dependent-tan
dependent-tan2mo ago
well i mean that's the default value that's what it's hardcoded to in facebook/react repo too hmm
continuing-cyan
continuing-cyan2mo ago
Oh, it was 1024 in the commit you linked In some places, at least I wonder how this plays with allReady though, I assuuuume it should be ignored?
dependent-tan
dependent-tan2mo ago
its always been 12,800 for the chunk size. the commit i linked is specifically the part that makes small boundaries get added together for the consideration which is what broke it for me
continuing-cyan
continuing-cyan2mo ago
got it, thanks for the explanation yea
dependent-tan
dependent-tan2mo ago
the only 1,024 is a fixture to test it thanks for checking to see if it did fix it! we should probably make this configurable but im unsure where I'd wanna put this..... @Manuel Schiller what do you reckon
following-aqua
following-aquaOP2mo ago
Nice find guys. Really impressive
continuing-cyan
continuing-cyan4w ago
Hello! Just checking if there's been an update regarding this point? Happy to be the one submitting the PR on this, just need to know what the TS team thinks is the best approach to fix this (ie a configuration? and where?) This is sadly devaluing the SEO benefits of SSR for us, so would like to look into it
continuing-cyan
continuing-cyan3w ago
https://github.com/TanStack/router/pull/5790 Submitted a PR for a config fix for this -- should be good enough for now i guess
GitHub
Add support for progressiveChunkSize by andyabih · Pull Request #5...
In reference to this: facebook/react@18212ca Currently, when the shell hits ~12.5KB, any suspense boundary larger than 500 bytes triggers an empty fallback. This results in pages loading without th...

Did you find this page helpful?