Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code splitting causes chunks to fail to load after new deployment for SPA

I have a single page app that I code split on each route. When I deploy a new version of my app the users will usually get an error if a user still has the page open and visits a route they haven't visited before.

Another scenario where this can also happen is if the app has service workers enabled. When the user visits a page after a new deployment, the service worker will serve from the cache. Then if the user tries to visit a page not in their cache, they'll get the chunk loading failure.

Currently I disabled code splitting in my app to avoid this but I've been very curious what's the best way to handle this issue. I've thought about pre-loading all the other routes after the user finishes loading the initial page and I believe this might fix the issue for code splitting on routes. But let's say I want to code split on components then that would mean I have to try to figure out when and how to pre-load all of those components.

So I'm wondering how do people handle this issue for single page apps? Thanks! (I'm currently using create-react-app)

like image 785
Kenneth Truong Avatar asked Jun 17 '17 05:06

Kenneth Truong


People also ask

What is a purpose of code splitting?

Code splitting allows you to break up the monolithic bundle into various smaller bundles. You can then load the bundles in parallel or implement lazy loading, delaying download of certain code until a user needs it.

How do I fix a chunk load error?

If you are encountering this error as a user of an application, the simplest way to resolve it is to clear your browser cache (and also restart it for good measure) and try again. If the error was because your browser had cached older files, this should fix the issue.

What can you use to handle code splitting?

Code-Splitting is a feature supported by bundlers like Webpack, Rollup and Browserify (via factor-bundle) which can create multiple bundles that can be dynamically loaded at runtime.

How does code splitting work react?

Code splitting is a way to split up your code from a large file into smaller code bundles. These can then be requested on demand or in parallel. Now, it isn't a new concept, but it can be tricky to understand.


1 Answers

I prefer to let the user refresh rather than refreshing automatically (this prevents the potential for an infinite refresh loop bug).
The following strategy works well for a React app, code split on routes:

Strategy

  1. Set your index.html to never cache. This ensures that the primary file that requests your initial assets is always fresh (and generally it isn't large so not caching it shouldn't be an issue). See MDN Cache Control.

  2. Use consistent chunk hashing for your chunks. This ensures that only the chunks that change will have a different hash. (See webpack.config.js snippet below)

  3. Don't invalidate the cache of your CDN on deploy so the old version won't lose it's chunks when a new version is deployed.

  4. Check the app version when navigating between routes in order to notify the user if they are running on an old version and request that they refresh.

  5. Finally, just in case a ChunkLoadError does occur: add an Error Boundary. (See Error Boundary below)

Snippet from webpack.config.js (Webpack v4)

From Uday Hiwarale:

optimization: {   moduleIds: 'hashed',   splitChunks: {       cacheGroups: {           default: false,           vendors: false,           // vendor chunk           vendor: {               name: 'vendor',               // async + async chunks               chunks: 'all',               // import file path containing node_modules               test: /node_modules/,               priority: 20           },       }   } 

Error Boundary

React Docs for Error Boundary

import React, { Component } from 'react'  export default class ErrorBoundary extends Component {   constructor(props) {     super(props);     this.state = { hasError: false };   }    static getDerivedStateFromError(error) {     // Update state so the next render will show the fallback UI.         return { hasError: true, error };   }   componentDidCatch(error, errorInfo) {     // You can also log the error to an error reporting service         console.error('Error Boundary Caught:', error, errorInfo);   } render() {     const {error, hasError} = this.state     if (hasError) {       // You can render any custom fallback UI             return <div>       <div>         {error.name === 'ChunkLoadError' ?           <div>             This application has been updated, please refresh your browser to see the latest content.           </div>           :           <div>             An error has occurred, please refresh and try again.           </div>}       </div>       </div>     }     return this.props.children;   } } 

Note: Make sure to clear the error on an internal navigation event (for example if you're using react-router) or else the error boundary will persist past internal navigation and will only go away on a real navigation or page refresh.

like image 159
Jordan Avatar answered Oct 17 '22 22:10

Jordan