Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Loadable Components increase Cumulative Layout Shift (CLS) #725

Closed
ludovic-lambert opened this issue Mar 28, 2021 · 11 comments
Closed
Labels

Comments

@ludovic-lambert
Copy link

ludovic-lambert commented Mar 28, 2021

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.01 to 0.6 or more.
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) })
...

Highlights:

  • Even with throttling to slow 3G, I don't see any visible CLS with Loadable Components enabled.
  • According to PageSpeed and LightHouse, it's the whole <main> section that is shifted, like if the SSR page is fully rendered dispite using hydrate.
  • The exact same code doesn't lead to layout shift without Loadable Components.

Config:

"dependencies": {
    "@loadable/component": "^5.14.1",
    "@loadable/server": "^5.14.2",
    "bnc-libs": "^0.2.33",
    "cookie-parser": "^1.4.5",
    "express": "^4.17.1",
    "isomorphic-fetch": "^3.0.0",
    "lazysizes": "^5.3.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^5.2.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.13.10",
    "@babel/core": "^7.13.13",
    "@babel/plugin-proposal-class-properties": "^7.13.0",
    "@babel/preset-react": "^7.13.13",
    "@babel/preset-typescript": "^7.13.0",
    "@loadable/babel-plugin": "^5.13.2",
    "@loadable/webpack-plugin": "^5.14.2",
    "@types/express": "^4.17.11",
    "@types/isomorphic-fetch": "0.0.35",
    "@types/loadable__component": "^5.13.3",
    "@types/loadable__server": "^5.12.3",
    "@types/node": "^14.14.37",
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.3",
    "@types/react-router-dom": "^5.1.7",
    "babel-loader": "^8.2.2",
    "jshint": "^2.12.0",
    "ts-node": "^9.1.1",
    "ts-node-dev": "^1.1.6",
    "typescript": "^4.2.3",
    "webpack": "^5.28.0",
    "webpack-cli": "^4.5.0",
    "webpack-node-externals": "^2.5.2"
  }
}
@open-collective-bot
Copy link

Hey @ludovic-lambert 👋,
Thank you for opening an issue. We'll get back to you as soon as we can.
Please, consider supporting us on Open Collective. We give a special attention to issues opened by backers.
If you use Loadable at work, you can also ask your company to sponsor us ❤️.

@ludovic-lambert ludovic-lambert changed the title React Loadable Components add Cumulative Layout Shift (CLS) React Loadable Components increase Cumulative Layout Shift (CLS) Mar 29, 2021
@theKashey
Copy link
Collaborator

theKashey commented Mar 29, 2021

There is no way I can help you with the information provided, however, I can ask you one question, which might lead to the root cause - are you using sync or async (Suspense) version?

Suspense powered loadable might cause CLS in React > 16.10 (facebook/react#16938 / atlassian-labs/react-loosely-lazy#46)

It is not "visible" to the eye, but all your content will be removed and instantly placed back.

@ludovic-lambert
Copy link
Author

ludovic-lambert commented Mar 29, 2021

Hi Anton, thank you for your help.
I'm not using Suspense at all.
The CLS increases whatever the code after is, even without any dynamically imported component.

With the exact same configuration, the CLS increase is high even if I import everything statically :

import Login from '../Account/Login'

If I import some components dynamically, the CLS increase is slightly higher or the same :

const Login = loadable(() => import('../Account/Login'), { ssr: true, fallback: <SpinLoader /> })

FYI <SpinLoader /> is nothing else than a div with CSS :

import React from 'react'
export const SpinLoader = () => {
  return (<div id="spinLoader" className="spinLoader"></div>)
}
export default SpinLoader

@theKashey
Copy link
Collaborator

In this case I've run out of ideas. And without an example is unable to move forward.

The only thing you have to check - that there are no hydration warnings and/or CSS is loaded in the correct order. You can use Performance tools/record page load in order to find this "shift".

@ludovic-lambert
Copy link
Author

What kind of exemple can I provide? Have you try to reproduce with any application?

@theKashey
Copy link
Collaborator

I don't have extra time even to go and merge a few awesome PRs here, so no, I have not. And you are the first one who reported the issue, so there is still a chance that it is somehow bound to your particular usecase.

@ludovic-lambert
Copy link
Author

It takes 3 minutes to switch SSR from Loadable-components to a standard React.

@stale
Copy link

stale bot commented Jun 2, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Jun 2, 2021
@stale stale bot closed this as completed Jun 9, 2021
@anilturan
Copy link

Hi @ludovic-lambert ,

I am having the same problem as you. The Loadble component has definetely impacted cls negatively. Have you found a solution to this issue?

@ludovic-lambert
Copy link
Author

HI @anilturan ,
Unfortunately not. Because of this issue and #726 , I've given up trying to use Loadable Component on my project.

@anilturan
Copy link

I guess I'll have to remove it too. Thank you for helping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants