Reputation: 604
I am deploying a project which was generated using CRA(Create-React-App), and all the babel and webpack configurations remain unmodified.
Also, I am deploying my React application using Static Bucket hosting feature of AWS S3.
The problem is, since there are no servers included, every time a new file is deployed, new chunk with random hashes are created.
When a client was in the website with old hash, and new hash was deployed, client gets Chunk Load Error.
To demonstrate this, build project with yarn build
and go to /build
folder to see chunks generated by webpack. Then using serve
, I can demonstrate how my deployment will behave in my local machine.
After yarn serve
was executed, go to appropriate localhost where build files are being served.
Then, at /build/static/js
folder, rename the hashes of each chunk file as you wish.
Leave the browser alone, and restart yarn serve
, which will serve renamed chunk files.
Then get back to the browser, and you will most likely get two errors:
Uncaught SyntaxError: Unexpected token '<'
Loading Chunk n failed.(missing: ~~)
The way I think can solve this problem is to HARD reload the webpage when ChunkLoadError occured. Sadly, window.reload(boolean)
function has been deprecated, and doing only window.location.reload()
doesn't solve this issue.
+) PS. I implemented Code-Splitting by using React.Lazy()
in some codes.
Upvotes: 4
Views: 13774
Reputation: 191
The solution you propose must work:
All you have to do is add these 2 meta tags to your index.html :
<meta http-equiv="Pragma" content="no-cache"/>
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate"/>
This will prevent the browser from using the cache when refresh your page
Second, use the ErrorBoundary component of react to put your refresh logic
Ex :
import React from "react";
const PageHasBeenForceRefreshed = "page-has-been-force-refreshed";
const retryPageLoading = () => {
const pageHasAlreadyBeenForceRefreshed = JSON.parse(
window.localStorage.getItem(PageHasBeenForceRefreshed) || "false"
) as boolean;
if (!pageHasAlreadyBeenForceRefreshed) {
window.localStorage.setItem(PageHasBeenForceRefreshed, "true");
return window.location.reload();
} else {
window.localStorage.setItem(PageHasBeenForceRefreshed, "false");
}
};
interface ErrorBoundaryProps {
children: React.ReactNode;
}
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, { hasError: boolean }> {
constructor(props: Readonly<ErrorBoundaryProps>) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error: unknown, info: unknown) {
retryPageLoading();
this.setState({ hasError: true });
console.log(error, info);
}
render() {
if (this.state.hasError) {
return <>Your error component</>;
}
return this.props.children;
}
}
When one chunk will be missing in the index.html, the error boundary will catch the error and run the function "retryPageLoading()". The function "retryPageLoading()" use the local storage to avoid infinite loop. When an error occurred and the page has not refresh yes, the refresh is done by "window.location.reload()"
Finally, wrap your react app in the ErrorBoundary, in order to catch the chunk error when it is missing
import * as React from "react";
import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary";
const App: React.FunctionComponent = () => {
return <ErrorBoundary>Your app</ErrorBoundary>;
};
Tell me if it is working. Good luck
Upvotes: 6