react-static: [Bug] "An error occurred loading this page's template" since v7

Reporting a bug?

Provide as much information as you can to reproduce the issue. If the issue is not reproducible, it can’t be fixed!

Environment

Run and copy the result of:

npx envinfo --system --npmPackages react* --binaries --npmGlobalPackages react* --browsers

here:


  System:
    OS: Linux 4.15 Ubuntu 18.04.2 LTS (Bionic Beaver)
    CPU: (4) x64 Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
    Memory: 177.36 MB / 7.52 GB
    Container: Yes
    Shell: 5.4.2 - /bin/zsh
  Binaries:
    Node: 10.15.3 - ~/.nvm/versions/node/v10.15.3/bin/node
    Yarn: 1.16.0 - /usr/bin/yarn
    npm: 6.4.1 - ~/.nvm/versions/node/v10.15.3/bin/npm
  Browsers:
    Chrome: 74.0.3729.169
    Firefox: 67.0
  npmPackages:
    react: ^16.8.6 => 16.8.6 
    react-dom: ^16.8.6 => 16.8.6 
    react-ga-donottrack: ^1.0.0 => 1.0.0 
    react-hot-loader: ^4.8.4 => 4.8.4 
    react-markdown: ^4.0.8 => 4.0.8 
    react-static: ^7.0.10 => 7.0.10 
    react-static-plugin-sass: ^7.0.10 => 7.0.10 
    react-static-plugin-typescript: ^7.0.10 => 7.0.10 
    react-syntax-highlighter: ^10.2.1 => 10.2.1 

of course include any other package versions here if relevant.

Steps to Reproduce the problem

Base your steps off of any freshly installed react-static template!

It’s a somewhat difficult problem because it doesn’t appear to be completely reproducible every time. These steps result in the problem some of the time. I have the feeling that it mostly occurs when the site is freshly loaded.

  1. Visit https://vincenttunru.com
  2. Click on one of the posts, e.g. “Fearless deployments”
  3. Hopefully see “An error occurred loading this page’s template. More information is available in the console.” The console then shows: “Error: export not found” in vendors~main.

image

When you then navigate to a different page, the page goes completely black with the message:

An internal error occured!

Please see the console for more details.

And the console says:

Invariant Violation: "Minified React error #130; visit https://reactjs.org/docs/error-decoder.html?invariant=130&args[]=undefined&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

image

It appears to be easier to reproduce when the site has not been visited for a couple of minutes…

This occurs in both Firefox and Chrome. I think it does not occur when the console is open. It also does not occur when you visit a page directly. Any pointers to other things I should investigate would be welcome.

Expected Behavior

The expected page should just be shown every time I click a link there.

Reproducible Demo

Source code of the website is here: https://gitlab.com/vincenttunru/blog

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 14
  • Comments: 40 (20 by maintainers)

Commits related to this issue

Most upvoted comments

Ok, here’s how to “fix” it. Although be aware, it’s a horrible hack.

Create a local plugin:

plugins/react-static-plugin-fix-universal-component/
├── node.api.js
└── react-universal-component
    ├── .babelrc.js
    ├── flowTypes.js
    ├── helpers.js
    ├── index.js
    ├── report-chunks.js
    ├── requireById
    │   └── index.js
    ├── requireUniversalModule.js
    └── utils.js

Plugin contains a copy of .js files from node_modules/react-universal-component/dist (very bad).

You need to edit plugins/react-static-plugin-fix-universal-component/react-universal-component/index.js and comment out this section in “preload” static function:

        // try {
        //   mod = requireSync(props, context);
        // } catch (error) {
        //   return Promise.reject(error);
        // }

Then node.api.js contents:

import path from "path";

process.env.REACT_STATIC_UNIVERSAL_PATH = path.join(__dirname, "react-universal-component/index.js");

export default () => ({
  webpack: (config, { stage }) => {
    if (stage === "prod") {
      config.resolve.alias["react-universal-component"] = path.join(__dirname, "react-universal-component/index.js");
    }
    return config;
  },
});

Then .babelrc.js:

module.exports = {
  sourceType: "unambiguous",
  presets: ["react-static/babel-preset.js"],
  plugins: [
    [
      require.resolve("@babel/plugin-transform-runtime"),
      {
        corejs: false,
        useESModules: true,
      },
    ],
    require.resolve("@babel/plugin-syntax-dynamic-import"),
  ],
};

While it all is a horrible hack, but that’s the essence of the problem there.

This is the bug I believe, had the same issue spent a few hours debugging: https://github.com/faceyspacey/react-universal-component/issues/198

tl;dr:

  1. periodic prefetch from react-static calls “preload” in react-universal-component
  2. “preload” tries sync path which ends up calling webpack_require if needed module is in the webpack’s table.
  3. if (2) fails, “preload” does a proper async module load
  4. webpack does async module load in parallel, loading the module itself and all of its deps, as soon as module is loaded (or one of its deps) it’s added to webpack module table
  5. if after (4) you call “preload” the second time, it tries sync path again and because module is already in the webpack’s table it tries to webpack_require it (but it’s possible that its deps are not there yet), even worse there is a try/catch in react-universal-component which swallows error in production, and as far as react-static goes, the bug is production only.
  6. due to the way webpack works, it actually defines module structure before running module init function (to be able to bind circular module deps properly) as a result what we get here is that module is defined, but module init function throws and then react-universal-component catches that exception and silently ignores it, module gets stuck in half-initialized state, it was added to webpack’s internal global variable, but wasn’t properly initialized
  7. after (6) when actual async import process finishes, webpack itself tries to perform webpack_require again, but this time empty module is returned and well effectively for a given module it’s just stuck in this half-initialized state

Hey @Vinnl, just wanted to say thank you for mentioning your migration to Gatsby. After hitting this (or a similar) issue for myself today (previously it had just been random user reports) and spending a bit of time trying to work out what was going on (non-starter - one of my browsers just seemed to be stuck with a “broken” version of the site which would randomly 404), I decided to try the migration and was actually amazed how easy it was, it took me about 90 minutes from first visiting the Gatsby website to having the whole site (only 6 templates) migrated!

You can actually use Gatsby with a very similar model to the static.config.js way if you don’t want to mess with GraphQL etc.

For anyone else who wants to try this, at a high level the changes I had to make were:

  • Install gatsby from npm
  • Create a gatsby-node.js file to fetch data and create routes using createPage as described in https://www.gatsbyjs.org/docs/using-gatsby-without-graphql/ - basically a straight migration of static-config.js. This means you aren’t using all the GraphQL stuff, but seemed the quickest way to migrate
  • Converted the site from having a top-level <App/> component with the global layout and then a <Router/> to one “template” file per page using a common <Layout/> component in there for global layout (was fairly easy as I already had “containers” for each page)
  • Converted the site from useSiteData/useRouteData to getting the data at the “template” level from props and passing it down (I prefer the way react-static does this, feels neater!)
  • Swap out reach-router <Link/> for the Gatsby implementation
  • Add plugins e.g. for styled components, Google Analytics

I can try to share more detail if anyone is interested.

It’s a shame to move away from react-static as I do prefer its API and simplicity, but this issue (and a couple of other random ones I had e.g. often adding a new component when editing a page required a restart of the dev server due to some “fibre” error) made it necessary as I can’t risk having client sites randomly erroring. The larger ecosystem and more “batteries included” approach of Gatsby is also a plus e.g. installing GA with a simple plugin, no need for hacks to ensure the page scrolls to the top correctly on navigation etc, and it seems the builds are faster.

Thanks to the react-static mantainers for all their work on this project, I don’t mean to be negative by posting this but just sharing my experiences for anyone else in a similar situation as it sounds like maintenance on react-static may be somewhat sporadic for the forseeable future.

Cheers, all the best, Tom

@tomduncalf As I was also having issues with this, I could answer your question.

I migrated all of the websites I had to work on (three of them) to gatsby. Gatsby is quite similar to react-static, there’s also a data model and a config thing where you can generate pages and all that. As for react-static, I’ve spent a lot of time trying to fix all the bugs myself with ugly and horrible hacks involving https://www.npmjs.com/package/patch-package, but at the end I never quite reached the point where it was behaving well. Some strange loading artifacts kept popping up, so I ended up with moving to gatsby. It worked out well. There’s one issue with gatsby though is that unlike react-static you can’t explicitly load page’s data, and in case if you need that feature, it can be done via hacks, e.g.:

export async function loadPage<T>(path: string): Promise<T> {
  const data = await (window as any).___loader.loadPage(path);
  return data.json.pageContext;
}

It’s sad that we had to move, but at the end of day we pick what works best.

People still use the above hack. It’s an upstream issue in react-universal-component.

Ok, thanks for the info.

Is there an alternative to using react-universal-component? If the upstream dependency is not going to fix the bug, then maybe react-static shouldn’t continue to use it…

Yeah, I’ve tried Gatsby and I moved to react-static from that. GraphQL is SOAP 2.0, IMHO. NextJS debug messages were unusably atrocious when I tried to move. React-static is simpler and I love it [for now].

Ok, here’s how to “fix” it. Although be aware, it’s a horrible hack.

Create a local plugin:

plugins/react-static-plugin-fix-universal-component/
├── node.api.js
└── react-universal-component
    ├── .babelrc.js
    ├── flowTypes.js
    ├── helpers.js
    ├── index.js
    ├── report-chunks.js
    ├── requireById
    │   └── index.js
    ├── requireUniversalModule.js
    └── utils.js

Plugin contains a copy of .js files from node_modules/react-universal-component/dist (very bad).

You need to edit plugins/react-static-plugin-fix-universal-component/react-universal-component/index.js and comment out this section in “preload” static function:

        // try {
        //   mod = requireSync(props, context);
        // } catch (error) {
        //   return Promise.reject(error);
        // }

Then node.api.js contents:

import path from "path";

process.env.REACT_STATIC_UNIVERSAL_PATH = path.join(__dirname, "react-universal-component/index.js");

export default () => ({
  webpack: (config, { stage }) => {
    if (stage === "prod") {
      config.resolve.alias["react-universal-component"] = path.join(__dirname, "react-universal-component/index.js");
    }
    return config;
  },
});

Then .babelrc.js:

module.exports = {
  sourceType: "unambiguous",
  presets: ["react-static/babel-preset.js"],
  plugins: [
    [
      require.resolve("@babel/plugin-transform-runtime"),
      {
        corejs: false,
        useESModules: true,
      },
    ],
    require.resolve("@babel/plugin-syntax-dynamic-import"),
  ],
};

While it all is a horrible hack, but that’s the essence of the problem there.

This is grate, it worked for me! i had to provide the plugin path, in static.config and not use the plugins folder. I made a plugin out of your code steps: zip file attached . This error was happing to me often, i had to double or triple refresh to get rid of it, after a new deployment on netify.

react-static-plugin-fix-universal-component.zip

Got ya - I hope the work comes back soon!

Just to emphasise @SleeplessByte, I’m very appreciative of you and your company stepping in to take over maintenance when that was most needed 😃 It’s just that, given the circumstances, migration to Next.js made sense; hope that didn’t come across as a dig at this project/your efforts, as that wasn’t the intention.

@Vinnl, thanks for the update. That’s a shame to hear 😦

Slightly off topic, how was the migration to Next.js? I’ve had quite a few random issues with this version of react-static (that I didn’t have with v6) that makes me wonder if I should just bite the bullet and migrate to something else – issues like this which can break the site and are not resolved are especially concerning (no offence intended to the maintainers).

I want to add to this issue: there is another possible problem going on.

You might want to comment out try/catch blocks with requireSync in “init” and “componentDidUpdate” functions as well. It’s not confirmed and I didn’t do a lot of research on this. But here’s the theory:

0.To refresh the memory: requireSync in general is just broken, as we know on async require webpack loads the package itself and its deps in parallel, checking if package is loaded doesn’t guarantee that it’s ready to be initialized. Webpack puts package object into its global table as soon as <script> is loaded, but its deps might still be in progress.

  1. Let’s say you have a website with an index page, which has some links to some other pages in a header.
  2. You load the index page, react-static preloading kicks in.
  3. Without waiting you click one of the links.
  4. Now you get to a page with another react universal component and as such those “init” and potentially due to some reason “componentDidUpdate” functions are called which do use “requireSync”.
  5. And since preloading is in progress at that moment this pretty much leads to the same bug as described in this issue.

Just another word of warning here. What react-universal-component does in “requireSync” is not correct. It makes assumptions about webpack that are not right. Please don’t make assumptions about webpack in general. If you do async require, then do async require always, don’t go to __webpack_modules__ and attempt to initialize/use the module yourself. Not to blame anyone, we all learn how to use async code properly in our codebases, I know it’s complicated and often it seems like having a synchronous path is a good and easy to reason about thing. But more often than not you just have to embrace the async.

Wow @nsf, just managed to read through your analysis, and that’s some excellent detective work you did there! Very much looking forward to not running into this bug any more.

Sorry for spamming everyone, but I really wanted to voice my appreciation here 😃

Taking a quick look in your code here… need that familiar with TS but it seems like you aren’t returning a component for your posts…

The react error also confirms that something is not returning a valid react element.

  const postRoutes = aliases.map((alias) => ({
            path: alias,
            redirect: postFilenameToPath(post.filename),
          }));

          routes = routes.concat(postRoutes);

You are only returning a path and a redirect value for your postRoutes.