44

I'm using React + Typescript with Webpack and I'm trying to load some of my react components when they are actually needed.

The issue is that when the chunk is requested via lazy loading I'm getting the following error:

Uncaught Error: Loading chunk 1 failed. (error: http://localhost:58988/1.chunk.js) at HTMLScriptElement.onScriptComplete (bootstrap:114)

This chunk is generated successfully and without any errors, it's just that the path is wrong - http://localhost:58988/1.chunk.js and it should be http://localhost:58988/dist/1.chunk.js

I've also tried using the newest React.lazy to lazy load react components but I'm getting the same issue. So, is there a way to tell the compiler how to resolve these file paths?

Here's some of the code:

MyComponent.tsx

import * as React from "react";
import * as ES5Promise from "promise";

export class MyComponent extends React.Component<{}, {}> {
    constructor(props) {
        super(props);
    }

    private readonly onLoadModuleClickHandler = (): void => {
        import("./Button").then((module) => {
            console.log(module);
        });
    }

    render() {
        return (
            <div>
                <input type="button" value="Load another module" onClick={this.onLoadModuleClickHandler}/>
            </div>
        );
    }
}

tsconfig.json

{
  "compilerOptions": {
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noEmitOnError": true,
    "module": "esnext",
    "removeComments": false,
    "sourceMap": false,
    "target": "es5",
    "jsx": "react",
    "noEmit": true,
    "importHelpers": true,
    "lib": ["dom", "es5", "es2015.promise"]
  },
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}

webpack.config.js

module.exports = {
    entry: {
        "app": "./src/App.tsx"
    },
    output: {
        path: path.resolve(__dirname, 'wwwroot/dist'),
        filename: "[name].bundle.js",
        chunkFilename: "[name].chunk.js"
    },
    module: {
        rules: [
            {
                test: /\.(ts|tsx)?$/,
                use: "awesome-typescript-loader",
                exclude: /node_modules/
            },
            {
                test: /\.(css|less)?$/,
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader?modules&localIdentName=[local]--[hash:base64:5]"
                }, {
                    loader: "less-loader"
                }]
            },
        ]
    },
    resolve: {
        extensions: [".js", ".jsx", ".ts", ".tsx", ".css", ".less"]
    }
};
Martin Shishkov
  • 2,709
  • 5
  • 27
  • 44

10 Answers10

47

That happens because output.publicPath by default is /.

Just update output.publicPath to point where you want it to be => /dist/.

PlayMa256
  • 6,603
  • 2
  • 34
  • 54
30

When redeploying, on rebuilding the app bundles make sure NOT to clean the output folder with previous chunk files, because users that already have the app loaded will try to fetch previous chunk files that will not exist anymore.

Best thing to do is to have an app version tracking (using AJAX and read from db or dynamic config file) and when the app detects a newer version, message the user about it and ask them to reload the page.

Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • 1
    +1 This is kinda late but I still remember that when I actually got these chunks working, I would often get a 404 error when trying to lazy load a component so eventually I opted out of this lazy loading stuff. When I'm reading your comment now it actually makes a lot of sense. – Martin Shishkov Aug 08 '20 at 09:22
  • 5
    I've had such issues in the past and I wrote this very late answer after seeing your question this year, to let people know that generated chunk files should never be deleted, or at least be kept for quite long after new chunk files get built. Yes, if you just think about it, it makes sense. People will have the app running with the bundle map when they loaded the app, so if some files in this map get deleted, then they'll get 404 when lazy loading kicks and try to get them. – Christos Lytras Aug 08 '20 at 13:24
10

Let's not over fixate on this. It only happened 5 times. I believe that somebody has excalidraw opened for a while, we push a new version in the meantime and when they get back, they try to load some lazy chunk and it is no longer there.

The way we solve this at Facebook is that we keep the previous chunks on our CDN for a week and we force refresh the app for people if the bundle is more than a week old. This way we don't need to care about very old versions.

The above response is from vjeux.

My plan is to listen for errors in window.onerror, and then prompt the user to refresh the browser.

window.addEventListener('error', e => {
  // prompt user to confirm refresh
  if (/Loading chunk [\d]+ failed/.test(e.message)) {
    window.location.reload();
  }
});
weiliang
  • 663
  • 8
  • 13
  • 6
    Wonder if this can ever cause an infinite loop.... guess would maybe need like a localstorage counter or something as the chunk might not be required for the page to mostly work – owenmelbz May 05 '21 at 08:33
  • 5
    Can confirm, we tried this trick and threw us into an infinite loop if you don't use a counter. – bvergara87 May 16 '21 at 08:09
4

In my case window.addEventListener('error'... didn't trigger so I overridden console.error method as follows:

    const origin = console.error;
    console.error = (error) => {
      if (/Loading chunk [\d]+ failed/.test(error.message)) {
        alert('A new version released. Need to relaod the page to apply changes.')
        window.location.reload();
      } else {
        origin(error);
      }
    }
Oleg Khalidov
  • 5,108
  • 1
  • 28
  • 29
  • I tried this out. But it's no longer working since Webpack sends the error as multiple arguments. And they are also strings and not Error objects. – Jompis Jun 21 '23 at 06:43
3

Setting the value of output.publicPath in webpack.config.js to / will fix the problem.

s0xzwasd
  • 2,895
  • 3
  • 17
  • 26
user3349907
  • 317
  • 3
  • 3
1

Another option can be simply use Magic Comment /* webpackMode: "eager" */ where we wish to load dynamically. Apparently it doesn't fetch missing chunk but takes from a bundle.

It does beat the purpose of the chunking in the first place, but it solves the problem.

Michal
  • 41
  • 5
1

When you use code splitting with React.lazy, sometimes a 'chunk load error loading chunk 4 failed in react' error will occur. If you refresh the page, the error will go away.You don't need to manual refresh if you follow the following steps.

This error does not occur if you use the following code in app.js to import pages

    const lazyRetry = function(componentImport) {

    return new Promise((resolve, reject) => {
 
  const hasRefreshed = JSON.parse(
      window.sessionStorage.getItem('retry-lazy-refreshed') || 'false'
  );
 
  componentImport().then((component) => {
      window.sessionStorage.setItem('retry-lazy-refreshed', 'false');
      resolve(component);
  }).catch((error) => {
      if (!hasRefreshed) { // not been refreshed yet
          window.sessionStorage.setItem('retry-lazy-refreshed', 'true');
          return window.location.reload(); // refresh the page
      }
      reject(error); 
  });
 });
};

import pages as

    const YourPage = React.lazy(()=> lazyRetry(() => import('./pages/YourPage')))
1

What solved this issue for me was setting the output.publicPath in webpack config to auto

https://webpack.js.org/guides/public-path/#automatic-publicpath

lboyel
  • 1,930
  • 4
  • 27
  • 37
0

When you use code splitting with React.lazy, sometimes a ChunkLoadError may occur. If you refresh the page, the error will go away. You can avoid having to refresh manually if you follow the following steps to load your components.

import { ComponentType, lazy } from "react";

function lazyRetry<T extends ComponentType<any>>(
  componentImport: Parameters<typeof lazy<T>>[0],
  chunkIdentifier: string
): Promise<{ default: T }> {
  const sessionStorageKey = `retry-lazy-refreshed-${chunkIdentifier}`;

  return new Promise((resolve, reject) => {
    // check if the window has already been refreshed due to this chunk
    const hasRefreshed = JSON.parse(
      window.sessionStorage.getItem(sessionStorageKey) || "false"
    );

    // try to import the component
    componentImport()
      .then((component) => {
        // success so reset the refresh state
        window.sessionStorage.setItem(sessionStorageKey, "false");
        resolve(component);
      })
      .catch((error) => {
        if (!hasRefreshed) {
          // not been refreshed yet
          window.sessionStorage.setItem(sessionStorageKey, "true");
          // refresh the page
          return window.location.reload();
        }

        // Default error behaviour as already tried refresh
        reject(error);
      });
  });
}

You can then import pages using

const YourPage = React.lazy(() =>
  lazyRetry(() => import("./pages/YourPage"), "YourPage")
);

This is a TypeScript version for the fix as found in https://www.codemzy.com/blog/fix-chunkloaderror-react

hbendev
  • 29
  • 4
0

In my case, I copied my all chunk files of dist into my project chunk directory and it resolved my issue.

Ziaur Rahman
  • 1,148
  • 11
  • 24