Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Failed to fetch dynamically imported module

I have a few React components that are lazy imported in App.tsx. App.tsx is used in Index.tsx where it is rendered and appended to the body.

const IndexPage = lazy(() => import("../features//IndexPage"));
const TagsPage = lazy(
  () => import("../features/tags/TagsPage")
);
const ArticlePage = lazy(() => import("../features/article/ArticlePage"));

const SearchResultPage = lazy(
  () => import("../features/search-result/SearchResultPage")
);

const ErrorPage = lazy(() => import("../features/error/ErrorPage"));
<BrowserRouter basename={basename}>
  <Suspense fallback={<Fallback />}>
    <Routes>
      <Route path={INDEX} element={<IndexPage />} />
      <Route path={ARTICLE} element={<ArticlePage />} />
      <Route path={TAGS} element={<TagsPage />} />
      <Route path={SEARCH} element={<SearchResultPage />} />
      <Route path={ERROR} element={<ErrorPage />} />
      <Route path="/*" element={<ErrorPage />} />
    </Routes>
  </Suspense>
</BrowserRouter>

Often, the following error happens in production:

Failed to fetch dynamically imported module:

It has happened in all routes:

https://help.example.io/static/js/SearchResultPage-c1900fe3.js
https://help.example.io/static/js/TagsPage-fb64584c.js
https://help.example.io/static/js/ArticlePage-ea64584c.js
https://help.example.io/static/js/IndexPage-fbd64584c.js

I changed the build path to /static/js.

build: {
  assetsInlineLimit: 0,
  minify: true,
  rollupOptions: {
    output: {
      assetFileNames: (assetInfo) => {
        var info = assetInfo.name.split(".");
        var extType = info[info.length - 1];
        if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
          extType = "img";
        } else if (/woff|woff2/.test(extType)) {
          extType = "css";
        }
        return `static/${extType}/[name]-[hash][extname]`;
      },
      chunkFileNames: "static/js/[name]-[hash].js",
      entryFileNames: "static/js/[name]-[hash].js",
    },
  },
  outDir: "./../backend/src/main/resources/static/articles/",
  emptyOutDir: true,
},

How can I fix this? I never got this error in development. I use Sentry to track errors. This is all that is on Sentry:

{
  arguments: [
    {
      message: Failed to fetch dynamically imported module: https://help.example.io/static/js/SearchResultPage-c1900fe3.js,
  name: TypeError,
    stack: TypeError: Failed to fetch dynamically imported module: https://help.example.io/static/js/SearchResultPage-c1900fe3.js
  }
],
  logger: console
}

I had 500,000 visits in the past two months. It happened 274 times. Since tracesSampleRate is 0.3 it is definitely more than that.

Sentry.init({
  dsn: "",
  integrations: [new BrowserTracing()],
  tracesSampleRate: 0.3,
});

It happened on all kinds of browsers but mostly Chrome. It happens if you are on a page and release a new version. The file that contains the dynamically imported module does not exist anymore, for e.g.:

https://help.example.io/static/js/IndexPage-fbd64584c.js

it returns 404.

like image 919
mahan Avatar asked Sep 08 '25 16:09

mahan


2 Answers

Here are a few alternative takes on the issue.

Avoiding the issue altogether.

This is stating the obvious, and I suspect that this may not be a desirable answer from your perspective, but switching to static imports will fix your issue.

In many cases, the bundle size, even with static imports, will still be small enough not to affect your user experience, and definitely less so than encountering the error.

It is also arguably the only guaranteed way to solve it as even if your issue is caused by a malfunction somewhere that you manage to track down and fix, there are still legitimate situation where this issue can happen.

Managing the error elegantly

On the other hand, still in the mind-set that the error can and will still happen, React has the concept of ErrorBoundaries which you can leverage to deal with the user experience when it occurs : https://reactjs.org/docs/error-boundaries.html and https://reactjs.org/docs/code-splitting.html#error-boundaries

Taking matters into your own hands

Finally, the React lazy expects a function that returns a promise that resolves to a module. If it does not, well that's your TypeError. It is obviously intended to be used exactly like you did, but you are free to wrap that in something smarter if you wish. All you need to do is make sure that the promise eventually resolves to the imported module. For inspiration, see here for quite a few takes on retrying promises. Promise Retry Design Patterns

like image 95
M. Gallant Avatar answered Sep 10 '25 06:09

M. Gallant


I think I might have found a good solution to the problem!

I was facing an identical situation. In my case, I use Vite in my React projects and every time the rollup generates the bundle chunks, it generates different hashes that are included in the filenames (Example: LoginPage.esm.16232.js). I also use a lot of code splitting in my routes. So every time I deployed to production, the chunk names would change, which would generate blank pages for clients every time they clicked on a link (which pointed to the old chunk) and which could only be resolved when the user refreshed the page (forcing the page to use the new chunks).

My solution was to create an ErrorBoundary wrapper for my React application that would "intercept" the error and display a nice error page explaining the problem and giving the option for the user to reload the page (or reload automatically)

like image 20
Lucas Novaes Avatar answered Sep 10 '25 04:09

Lucas Novaes