gregberge / loadable-components

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

How to avoid preloading certain routes #408

Closed Aleksion closed 5 years ago

Aleksion commented 5 years ago

💬 Questions and Help

Hi,

I'm trying to optimize our page speed as much as possible, and one of the things that seem to be hurting it right now is the fact that ALL of the applications scripts are preloaded - even though only a few are needed for the initial screens. It hikes up the execution time of javascript, everytime I add a new route. My question is, is there a way to do code splitting, BUT defer fetching the bundles until the user navigates to a screen?

My guess is that I'm configuring things the wrong way in my router, but I havne't managed to hit a configuration that doesn't result in all the chunks being listed as required and loaded here:

Screen Shot 2019-08-13 at 3 29 59 AM

This is what my Router looks like:

// External Imports
import * as React from "react"

import loadable from "@loadable/component"
import { Redirect, RouteComponentProps, Router } from "@reach/router"
// Project Imports

// Local Imports

/*******************************************
 *** Types
 ******************************************/

/*******************************************
 *** Helpers
 ******************************************/
export const BaseRoute = (props: RouteComponentProps & { children: any }) => (
  <>{props.children}</>
)
/*******************************************
 *** Routes
 ******************************************/
const HomeScreen = loadable<RouteComponentProps>(() => import("./screens/Home"))
const PartnerScreen = loadable<RouteComponentProps>(() =>
  import("./screens/BecomeAPartner")
)
const ReviewsScreen = loadable<RouteComponentProps>(() =>
  import("./screens/Reviews")
)
const SupportScreen = loadable<RouteComponentProps>(() =>
  import("./screens/Support")
)
const TermsScreen = loadable<RouteComponentProps>(() =>
  import("./screens/Terms")
)
const PrivacyScreen = loadable<RouteComponentProps>(() =>
  import("./screens/Privacy")
)
const CityScreen = loadable<RouteComponentProps>(() => import("./screens/City"))
const PlaceScreen = loadable<RouteComponentProps>(() =>
  import("./screens/Place")
)
const CitiesScreen = loadable<RouteComponentProps>(() =>
  import("./screens/Cities")
)

const PartnershipTerms = loadable<RouteComponentProps>(() =>
  import("./screens/PartnershipTerms")
)

// Blog
const BlogScreen = loadable<RouteComponentProps>(() => import("./screens/Blog"))
const BlogSearchScreen = loadable<RouteComponentProps>(() =>
  import("./screens/Blog/Search")
)

const BlogPostScreen = loadable<RouteComponentProps>(() =>
  import("./screens/Blog/Post")
)

/*******************************************
 *** Export Router
 ******************************************/
const AppRouter = () => (
  <Router primary={false}>
    {/* Handle Home Screens */}
    <HomeScreen path="/*" />

    <PartnerScreen path="/partner" />
    <ReviewsScreen path="/reviews" />
    <SupportScreen path="/support" />
    <TermsScreen path="/terms" />
    <PrivacyScreen path="/privacy" />

    {/* Parntership Terms */}
    <Redirect
      from="/legal"
      to="/partnership-standard-terms"
      state={{ status: 301 }}
    />
    <PartnershipTerms path="/partnership-standard-terms" />
    <CityScreen path="/city/:city" />
    <PlaceScreen path="/city/:city/:place" />
    <CitiesScreen path="/cities" />

    <BaseRoute path="/blog">
      <BlogScreen path="/" />
      <BlogSearchScreen path="/search" />
      <BlogPostScreen path="/post/:post" />
    </BaseRoute>
  </Router>
)

export default AppRouter

And my SSR configuration is as follows:

...
/*******************************************
     *** Run loadable component extractor
     ******************************************/
    const webExtractor = new ChunkExtractor({
      statsFile: webStats,
      entrypoints: ["client"],
    })
    const sheet = new ServerStyleSheet()
    const jsx = webExtractor.collectChunks(sheet.collectStyles(element))

    // Start sending a response to the client
    res.write(`
    <!doctype html>
    <html>
    <head>
    <title>Bounce</title>
    <meta
    name="viewport"
    content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
  />

   `)

    renderToStringWithData(jsx)
      .then(content => {
        const nativeCssTags = renderToStaticMarkup(getStyleElement())
        const initialApolloState = client.cache.extract()
        const helmet = Helmet.renderStatic()

        res.write(`
        ${webExtractor.getLinkTags()}
        ${nativeCssTags}
        </head><body>
        `)

        const html = (
          <Document
            content={content}
            apolloState={initialApolloState}
            reduxState={initialReduxState}
          />
        )

        const stream = sheet.interleaveWithNodeStream(
          renderToStaticNodeStream(html)
        )
        stream.pipe(
          res,
          { end: false }
        )

        // When React finishes rendering send the rest of your HTML to the browser
        stream.on("end", () => {
          res.end(`${webExtractor.getScriptTags()}</body></html>`)
        })
        // const markup = renderToStaticMarkup(html)

        // res.send(`<!doctype html>${markup}`)
      })
      .catch(err => {
        sheet.seal()
        if (isRedirect(err)) {
          res.redirect(301, err.uri)
        } else {
          res.status(500).send()
        }
      })
  })

Loadable Components project is young, but please before asking your question:

After you can submit your question and we will be happy to help you!

gregberge commented 5 years ago

Hello @Aleksion, in your example, all components are mounted so all components are loaded. You have to find a way to avoid mounting all components. I think you should probably ask on @reach/router because it is very specific to this project. Using react-router you don't have this kind of issue because the component is only mounted if the route matches.