we have got our ecommerce platform already in production and we are experiencing weird ChunkLoadError. This error happens randomly, and is not replicable. When we are trying to open failed file it is there and can be loaded normaly.
If user get's this error, he get's white screen (logicaly) but after refresh everything is fine.
We are running SSR ecommerce on React (latest), Express (latest)
our webpack / razzle config
const path = require('path');
const autoprefixer = require('autoprefixer');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const LoadablePlugin = require('@loadable/webpack-plugin');
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const TerserPlugin = require('terser-webpack-plugin');
// const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
module.exports = {
modify: (baseConfig, env, webpack) => {
const { target, dev } = env;
const appConfig = { ...baseConfig };
// Setup SCSS
if (target === 'web') {
const filename = path.resolve(__dirname, 'build');
const cssLoader = {
loader: 'css-loader',
options: {
minimize: !dev,
sourceMap: false,
importLoaders: 1
}
};
const postCSSLoader = {
loader: 'postcss-loader',
options: {
ident: 'postcss',
sourceMap: false,
plugins: () => [
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9' // React doesn't support IE8 anyway
]
})
]
}
};
const sassLoader = {
loader: 'sass-loader',
options: {
minimize: !dev,
sourceMap: false,
importLoaders: 1
}
};
if (dev) {
appConfig.output.filename = 'static/js/[name].js';
appConfig.module.rules.push({
test: /\.scss$/,
use: ['style-loader', cssLoader, postCSSLoader, sassLoader]
});
} else {
appConfig.output.filename = 'static/js/[name].[chunkhash:8].js';
// For production, extract CSS
appConfig.module.rules.push({
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, cssLoader, postCSSLoader, sassLoader]
});
appConfig.plugins.push(
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.IgnorePlugin(/moment/, /react-kronos/),
new webpack.optimize.OccurrenceOrderPlugin(),
// new webpack.optimize.LimitChunkCountPlugin({maxChunks: 50}),
new CompressionPlugin()
,new BundleAnalyzerPlugin({
analyzerMode: 'static',
generateStatsFile: true,
openAnalyzer: false
})
// ,new DuplicatePackageCheckerPlugin()
);
}
// optimization
appConfig.optimization = {
...baseConfig.optimization,
minimize: !dev,
minimizer: [new TerserPlugin({
parallel: true,
})],
splitChunks: {
chunks: 'initial',
minSize: 30000,
// minRemainingSize: 0,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: '~',
automaticNameMaxLength: 30,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
},
moduleIds: 'total-size', //added in future deterministic
chunkIds: 'total-size', //added
mangleWasmImports: !dev, //added
removeAvailableModules: !dev, //added
mergeDuplicateChunks: !dev, //added
flagIncludedChunks: !dev,
occurrenceOrder: false,
usedExports: !dev,
// namedModules: true,
// namedChunks: true,
runtimeChunk: 'single'
// runtimeChunk: {
// name: entrypoint => `runtimechunk~${entrypoint.name}`
// }
};
appConfig.plugins.push(
new LoadablePlugin({
outputAsset: false,
writeToDisk: { filename }
}),
new LodashModuleReplacementPlugin({
collections: true,
cloning: true,
deburring: true,
// coercions: true,
flattening: true,
paths: true,
// placeholders: true
shorthands: true
// caching: true
})
);
} else {
appConfig.module.rules.push({
test: /\.(scss)$/,
use: ['ignore-loader']
});
}
return appConfig;
},
modifyBabelOptions: mainBabelOptions => {
return {
...mainBabelOptions,
...{ plugins: [].concat(mainBabelOptions.plugins ? mainBabelOptions.plugins : [], ['lodash']) }
};
}
};
Here is randomly picked trace
(error: https://www.freshbox.sk/static/js/common-blocks-functional-userButton.3074d9ca.chunk.js)
at m.e(webpack/bootstrap:170:18)
at importAsync(./src/common/blocks/header/HeaderVariant2.jsx:35:9)
at requireAsync(./src/common/blocks/header/HeaderVariant2.jsx:34:28)
at loadAsync(./node_modules/@loadable/component/dist/loadable.esm.js:217:31)
at componentDidMount(./node_modules/@loadable/component/dist/loadable.esm.js:147:16)
at Ji(./node_modules/react-dom/cjs/react-dom.production.min.js:212:132)
at b(./node_modules/react-dom/cjs/react-dom.production.min.js:255:229)
at If(./node_modules/scheduler/cjs/scheduler.production.min.js:19:467)
at cg(./node_modules/react-dom/cjs/react-dom.production.min.js:122:325)
at Jj(./node_modules/react-dom/cjs/react-dom.production.min.js:248:370)
at yj(./node_modules/react-dom/cjs/react-dom.production.min.js:239:376)
at Ig(./node_modules/react-dom/cjs/react-dom.production.min.js:230:137)
at bk(./node_modules/react-dom/cjs/react-dom.production.min.js:281:43)
at a(./node_modules/react-dom/cjs/react-dom.production.min.js:284:301)
at Nj(./node_modules/react-dom/cjs/react-dom.production.min.js:240:120)
at ik(./node_modules/react-dom/cjs/react-dom.production.min.js:284:287)
at hydrate(./node_modules/react-dom/cjs/react-dom.production.min.js:290:206)
at done(./src/client/index.js:81:3)
at checkReadyState(./node_modules/@loadable/component/dist/loadable.esm.js:428:11)
at E/</n.push(./node_modules/@loadable/component/dist/loadable.esm.js:435:7)
at ? (/static/js/common-components-category-listing-_default-LayoutSwitcher.869947cb.chunk.js:1:75)```
If the code is split into chunks in order to optimise the loading, the index file usually contains the names and path to all the chunks according to the current webpack build. Making changes to the code and building again can rename the chunks with edited code. The browser however loads the index first along with certain required chunks, and then the rest of the chunks are fetched on demand, to make the whole code-splitting optimisation work.
I suspect that in this case, there is a production deploy after the index is loaded by the browser, and some of the chunks get renamed by the changes next build. Which results in invalid addresses for certain chunks in the stale index. Afterwards when the old index which was in the browser tries to load those non-existent chunks when the user navigated to that part of the website, it throws Loading chunk XY failed
. Refreshing should update the index and resolve the issue.
One way to resolve this would be to use service workers. The way it could work is as follows:
Using the stale-while-revalidate or cache-first logic for service workers should work here. Hope that helps.