React: Loadable Components increase Cumulative Layout Shift (CLS)
Asked Answered
D

1

8

After migrating a server side rendering React application to Loadable Components for code splitting and lazy loading, the initial bundle size, thus its download time, reduced as expected. However after replacing the classical React rendering method by the Loadable Components one, with the rest of the application code unchanged, my Cumulative Layout Shift score in PageSpeed / LightHouse raised to the sky, from 0.05 to 1 or more. I mean even without dynamically loading any component, things are worst otherwise.

What I am doing wrong?

SSR code before Loadable Components (good CLS score) :

Server-side :

...
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom'
...
const ssrApp = renderToString(
  <StaticRouter location={`/${this.requestedPage.uri}`} context={{}}>
    <App />
  </StaticRouter>
)

Client-side :

...
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import { hydrate } from 'react-dom'
...
hydrate(<BrowserRouter><App /></BrowserRouter>, appRoot)
...

SSR after Loadable Components (bad CLS score) :

Server-side :

...
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom'
import { ChunkExtractor } from '@loadable/server'
import path from 'path'
...
const statsFile = path.resolve(`${process.env.APP_ROOT}${path.sep}public${path.sep}js${path.sep}loadable-stats.json`)
const extractor = new ChunkExtractor({ statsFile, publicPath: '/js' })
const jsx = extractor.collectChunks(
  <StaticRouter location={`/${this.requestedPage.uri}`} context={{}}>
    <App user={currentUser} requestedPage={this.requestedPage} />
  </StaticRouter>
)
const scriptTags = extractor.getScriptTags()
const ssrApp = renderToString(jsx)
...

Client-side :

...
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import { hydrate } from 'react-dom'
import { loadableReady } from '@loadable/component'
...
loadableReady(() => { hydrate(<BrowserRouter><App /></BrowserRouter>, appRoot) })
...

Notes :

  • According to PageSpeed and LightHouse, it's the whole HTML <main> section of the code (filled with components routed with React Router) that is shifted, as if the SSR page was fully rerendered dispite using hydrate.
  • The exact same code doesn't lead to layout shift without Loadable Components.
Dearr answered 28/3, 2021 at 16:53 Comment(3)
I am seeing the issue with React.lazy() & CLS. Removing React.lazy() improves CLS, but penalises with higher FCP, Speed Index & LCP. Adding back React.lazy() helps improve FCP, LCP & SI, but costs in terms of CLS. Would appreciate insights from anyone who has solved this catch-22.Rag
Did you ever find an answer for this?Miki
I ended up using prerender.io. That helped improve Core Web Vitals score.Rag
M
0

If you know the height and width of the lazy div, set the minimum height and width for container of lazy div. So user will not see any shift in their layouts. In the centre of the div you can place loading or spinner icon.

This way user will not see and shift in their layouts and indeed results in better user experience.

Note - If size of the lazy div is varies, use average values so the shift will be less and reduce CLS impact score.

.container{
  min-height: 500px;
  min-width: 800px;
}

<div class="container">
  <LazyDiv />
</div>
Meteorograph answered 9/10, 2022 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.