Prerender fails because of ESM import in unrelated layout


When running yarn rw build web our project fails to build because an ES module import is done in an unrelated layout not used by the pages we are trying to prerender. I found this related topic on prerendering, but their problem was importing ES modules directly in prerendered pages.

Our repository and code look like this (if this is a bug, I can upload a minimal reproduction to help solve the issue).

- api/
- packages/
  - shared/
- web/
// routes.tsx
import { Set, Router, Route } from '@redwoodjs/router'

import DashboardLayout from 'src/layouts/DashboardLayout'
import Footer from 'src/layouts/Footer'
import Header from 'src/layouts/Header'

const Routes = () => {
  return (
      <Set wrap={[Header, Footer]}>
        <Route path="/" page={LandingPage} name="landingPage" prerender />
        <Route path="/about" page={AboutPage} name="aboutPage" prerender />
        <Route path="/contact" page={ContactPage} name="contactPage" prerender />
      <Set wrap={[DashboardLayout]}>
        <Route path="/team/{slug}" page={DashboardPage} name="dashboardPage" />
        {/* ... more */}

packages/shared contains utils functions we use in web and api. This has been working for a good while until we started importing the package in DashboardLayout and DashboardPage which causes the following error when prerendering:

✔ Generating Prisma Client...
✔ Building Web...

Creating 200.html...
Starting prerendering...
✖ Failed to render "repo\web\src\pages\AboutPage\AboutPage.tsx"
◼ Prerendering /contact -> web/dist/contact.html

You can use `yarn rw prerender --dry-run` to debug
---------- Error rendering path "/about" ----------
Error [ERR_REQUIRE_ESM]: require() of ES Module repo\packages\shared\dist\src\index.js from repo\web\src\layouts\DashboardLayout\DashboardLayout.tsx not supported.
Instead change the require of index.js in repo\web\src\layouts\DashboardLayout\DashboardLayout.tsx to a dynamic import() which is available in all CommonJS modules.
    at Object.newLoader [as .tsx] (repo\node_modules\pirates\lib\index.js:121:7)
    at Object.<anonymous> (repo\web\src\layouts\DashboardLayout\DashboardLayout.tsx:10:58)
    at Module._compile (repo\node_modules\pirates\lib\index.js:117:24)
    at Object.newLoader [as .tsx] (repo\node_modules\pirates\lib\index.js:121:7)
    at Object.<anonymous> (repo\web\src\Routes.tsx:18:57)
    at Module._compile (repo\node_modules\pirates\lib\index.js:117:24)
    at Object.newLoader [as .tsx] (repo\node_modules\pirates\lib\index.js:121:7)
    at Object.<anonymous> (repo\web\src\App.tsx:12:38)
    at Module._compile (repo\node_modules\pirates\lib\index.js:117:24)
    at Object.newLoader [as .tsx] (repo\node_modules\pirates\lib\index.js:121:7)
    at runPrerender (repo\node_modules\@redwoodjs\prerender\dist\runPrerender.js:257:7)
    at async _Task.task [as taskFn] (repo\node_modules\@redwoodjs\cli\dist\commands\prerenderHandler.js:158:35)
    at async (repo\node_modules\listr2\dist\index.cjs:2049:11)

Reading the stack trace it seems to occur because Routes.tsx imports DashboardLayout.tsx and so on. I have tried to use some of the tricks mentioned in Prerender | RedwoodJS Docs to prevent the import, but it wasn’t possible.

Any help on the issue is greatly appreciated.

Hi @lars

Would it be possible to do a conditional import of the stuff you need from your shared package inside DashboardLayout?

Hi @Tobbe

Yeah, I think so. Would it be something like:

// DashboardLayout.tsx
import { isBrowser } from '@redwoodjs/prerender/browserUtils'

const DashboardLayout = () => {
  if (isBrowser) {
    import('shared').then((shared) => {/* logic */})
    // or
    const shared = await import('shared')
  // ...
  // return jsx

export default DashboardLayout

or did you have something else in mind? We use the await import() method in the api directory as Redwood still does not support importing ESM: ES modules aren't supported · Issue #4074 · redwoodjs/redwood · GitHub

Yeah, exactly. Something like that :slight_smile:

Please try that and let me know how it goes

@Tobbe it did work, however, it was not our preferred solution, so we made some changes to how we consume our shared package so that it is not imported in the layout which gets imported in Routes.tsx during prerender.

But back to the original issue, do you know why the build fails in the first place? I thought ES modules were supposed to be fully supported with Vite and Vite is used to build the web package.

Yeah, vite definitely support ESM. But we don’t yet :slightly_frowning_face: We’re unfortunately stuck on Vite 4 because of it. For Vite 5 they’re ESM only, which we aren’t prepared for yet.

There was some experimenting going on just earlier today by Danny, but he seems to have quickly dismissed it :sweat_smile: (Do not merge): Try swtiching rwjs/vite to esm output only by dac09 · Pull Request #9555 · redwoodjs/redwood · GitHub

1 Like

Alright. I will be looking out for that in the future. :smiley:

I’d love to contribute, but upgrading to Vite 5 is probably a bit of a mouthful for a new contributor, I would be happy to test whenever the team gets to that point.

1 Like