gregberge / loadable-components

The recommended Code Splitting library for React ✂️✨
https://loadable-components.com
MIT License
7.68k stars 380 forks source link

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

Closed ludovic-lambert closed 3 years ago

ludovic-lambert commented 3 years ago

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:

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[bot] commented 3 years ago

Hey @ludovic-lambert :wave:, 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 :heart:.

theKashey commented 3 years ago

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 (https://github.com/facebook/react/issues/16938 / https://github.com/atlassian-labs/react-loosely-lazy/issues/46)

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

ludovic-lambert commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

theKashey commented 3 years ago

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 commented 3 years ago

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

stale[bot] commented 3 years ago

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.

anilturan commented 2 years ago

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 commented 2 years ago

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

anilturan commented 2 years ago

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