Root Layout export #8702
Closed
ryanflorence
announced in
Official RFCs
Replies: 2 comments 6 replies
-
With a default layout, would It makes sense to me, since |
Beta Was this translation helpful? Give feedback.
3 replies
-
Added in #8709 |
Beta Was this translation helpful? Give feedback.
3 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
The rendered root of the document is not a stable component type causing issues with the UX, primarily FOUC when
<Links />
is removed and re-added. With the introduction of SPA mode, this UX issue is now critical to fix.In the root route you can have the default export, an error boundary, and a hydrate fallback.
Each of these are rendered as the root of the app at different points in time:
Because the
element.type
changes when we flip from the HydrateFallback to the App, or from the App to the ErrorBoundary, all of the HTML is removed and remounted. React's reconciliation does not diff child elements of different component types (nor should it), for example:React sees
element.type
has switched fromdiv
tomain
and replaces it causingMyCoolComponent
to unmount and then mount again with a new parent. In the case of the root.tsx module, our entire document is replaced, including the<Links/>
Why this causes FOUC
In the case of stylesheets, the browser stops applying the styles when React removes the
<Links/>
from the previous root component, and then redownloads the styles when<Links />
is mounted again.The problem is that the UI has been updated and rendered before the stylesheets have been downloaded again. This is different from an initial page load because the browser does not paint the page until all stylesheets have been loaded. When you're swapping styles in an out of an already loaded page it doesn't know how to wait.
With a route transition, Remix knows how to wait for any styles that are in the next route, but it can't know that for the root element switching from one type to another.
Previously this behavior was only noticed when an app had an unexpected error and the root error boundary was rendered. Since the app was already blowing up, the FOUC wasn't a major priority, or even really noticed by most developers since it seems many just use the default error boundary provided by Remix.
With clientLoaders, SPA mode, and HydrateFallback, this is now a major issue.
Solution
We simply need a stable root component that doesn't get unmounted when the app changes between the three states.
Today's workaround
Apps today can create a layout route in
routes/
to fix thisRoot.tsx would look something like this:
And _layout.tsx
Now the
root.tsx
with the<html>
,<head>
, and<Links/>
will be stable as_layout
flips between states, avoiding the problem.Today's convention
I think every production Remix app I've seen has a
Layout
component in root.tsx to avoid repeating all of the html document parts:This doesn't help the FOUC problem, but it's relevant to the proposal below.
Proposal
Instead of asking every Remix app to have a
routes/_layout.tsx
to fix the FOUC problem, we should embrace the community convention of the rootLayout
component.If the app has supplied a
Layout
export in the root route, we'll use that as the root component. As the app swaps between the root default export, error boundary, and hydrate fallback in the layoutchildren
prop, the<Links/>
remain stable.Not only does this solve the problem, but most apps are already set up to use this API: simply export the
Layout
component they already have.Can all routes export a layout?
No. They are already layouts themselves, this is only needed at the root since it has the unique challenge of being the definition of the entire document.
Default Layout
Remix could provide a default layout when none is supplied that looks like this:
And then apps wouldn't need to worry about the default document if they didn't want to.
Beta Was this translation helpful? Give feedback.
All reactions