<aside> 🧑🎓 To eliminate blocking Suspense, you need to know what components are critical. Do it by identifying the critical rendering path:
How to identify critical rendering path?
</aside>
Unblocking suspense means making something critical wrapped into the same Suspense element with non-critical be independent of each other. For example, such code:
<aside>
🧠 In React, exists a concept of lazy-loadable components. Such components are rendered by wrapping them with a Suspense
utility from React. Suspense renders fallback, until all lazy components are not loaded.
</aside>
<Suspense>
<LazySomething />
<LazyHeader />
<LazyFooter />
<LazyUrlRewrites /> // <-- this is only critical
</Suspense>
Would result in following request chain:
Notice the blocking period cause by something
, footer
and header
chunks. The render did not happen, as Suspense
component is waiting for all of it’s children to resolve, before rendering (all of them at once). By separating critical elements and non-critical elements into their own suspense, we can unblock the critical element rendering:
<Suspense>
<LazySomething />
<LazyHeader />
<LazyFooter />
</Suspense>
<Suspense>
<LazyUrlRewrites /> // <-- this is only critical
</Suspense>
This will result in following timeline:
<aside> 🧠 When should Suspense unblocking be done? It is recommended to do it as soon as possible, to avoid having a gap caused by non-critical dependencies in your critical request chain. Also, the after-priority-load relies on separate Suspenses for critical and non-critical components.
</aside>
To eliminate blocking Suspense, means to eliminate Suspense
components wrapping multiple lazy components at once, preventing other children from rendering immediately. To achieve it, the following actions are required:
Search the source-code of the application for <Suspense
mentions. Make sure it does not contain a mixture of critical and non-critical child component. Learn which components are critical by learning the critical rendering path:
<aside>
🧠 For context, let’s consider the LazyHeader
and LazyFooter
are lazy React components, while the Content
and Loader
are normal Components. Lazy components should be wrapped with Suspense
. It might be tempting to implement such case as follows (wrapping all content):
<Suspense fallback={<Loader />}>
<LazyHeader />
<Content />
<LazyFooter />
</Suspense>
The problem in such case is the fact that Suspense
is rendering fallback until all lazy child components are loaded. This means Content
will not be rendered, until LazyHeader
and LazyFooter
are loaded.
Even worse, if the Content
contains lazy components not wrapped with Suspense
, they will trigger the next-closest-parent suspense, in this case, above the Content
, preventing the render of it, despite Content
itself NOT being lazy.
The solution is to wrap each lazy component with it’s own Suspense
, like so:
<Suspense fallback={<Loader />}>
<LazyHeader />
</Suspense>
<Content />
<Suspense fallback={<Loader />}>
<LazyFooter />
</Suspense>
</aside>
Locate a component where lazy component is imported and should render. Add a Suspense
render around a place where lazy component is suppose to render. Provide a fallback (preferably avoid Loader
component, and render div
). This simple fallback will be replaced with a proper placeholder in during CLS optimization.
‣
Override the src/component/Router/Router.component
component, specifically methods:
<aside> 🧠 The code bellow, apart from adding Suspense, also removes the loader from the page. Leaders make user experience worse. Learn more in a guide teaching how to eliminate them off pages:
</aside>
renderMainItems() {
return (
// Wrap the Switch with suspense (content fallback)
<Suspense fallback={ <div /> }>
{ super.renderMainItems() }
</Suspense>
);
}
renderComponentsOfType(type) {
if (type === SWITCH_ITEMS_TYPE) {
// Route component must be Switch direct child => no Suspense here
return super.renderComponentsOfType(type);
}
return this.getSortedItems(type)
.map(({ position, component }) => {
return (
// Wrap each section component with Suspense
<Suspense fallback={ <div /> }>
{ cloneElement(component, { key: position }) }
</Suspense>
);
});
}
<aside> 🎉 This is how you eliminate render-blocking suspense!
</aside>