Redwood 3.0.1 prerender error

Hey I could use some help in resolving this error:

yarn rw prerender --dry-run   
::: Dry run, not writing changes :::

You can use `yarn rw prerender --dry-run` to debug

---------- Error rendering path "/login" ----------
Error [ERR_REQUIRE_ESM]: require() of ES Module C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@simplewebauthn\browser\dist\bundle\index.js from C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@redwoodjs\auth\dist\webAuthn\index.js not supported.
Instead change the require of C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@simplewebauthn\browser\dist\bundle\index.js in C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@redwoodjs\auth\dist\webAuthn\index.js to a dynamic import() which is available in all CommonJS modules.
    at Object.newLoader [as .js] (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\pirates\lib\index.js:141:7)
    at Object.<anonymous> (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@redwoodjs\auth\dist\webAuthn\index.js:15:16)
    at Module._compile (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\pirates\lib\index.js:136:24)
    at Object.newLoader [as .js] (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\pirates\lib\index.js:141:7)
    at Object.<anonymous> (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@redwoodjs\auth\webAuthn\index.js:2:18)
    at Module._compile (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\pirates\lib\index.js:136:24)
    at Object.newLoader [as .js] (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\pirates\lib\index.js:141:7)
    at Object.<anonymous> (C:\Users\basti\WebstormProjects\v3Players2Play\web\src\App.tsx:14:40)
    at Module._compile (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\pirates\lib\index.js:136:24)
    at Object.newLoader [as .tsx] (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\pirates\lib\index.js:141:7)
    at C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@redwoodjs\prerender\dist\runPrerender.js:179:123
    at async runPrerender (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@redwoodjs\prerender\dist\runPrerender.js:179:7)
    at async Task.task (C:\Users\basti\WebstormProjects\v3Players2Play\node_modules\@redwoodjs\cli\dist\commands\prerenderHandler.js:195:35)

  × Prerendering /login -> web/dist/login.html
    → Failed to render "C:\Users\basti\WebstormProjects\v3Players2Play\web\src\pages\LoginPage\LoginPage.tsx"
    Prerendering /signup -> web/dist/signup.html
    Prerendering /forgot-password -> web/dist/forgot-password.html
    Prerendering /reset-password -> web/dist/reset-password.html
    Prerendering /games -> web/dist/games.html
    Prerendering /game/{name:slug} -> web/dist/game/{name:slug}.html
    Prerendering / -> web/dist/index.html
    Prerendering /404 -> web/dist/404.html
Running diagnostic checks
✔ Diagnostics checks passed

Here is my /login Page:

import { useRef, useState } from 'react'
import { useEffect } from 'react'

import { useAuth } from '@redwoodjs/auth'
import {
  Form,
  Label,
  TextField,
  PasswordField,
  Submit,
  FieldError,
} from '@redwoodjs/forms'
import { Link, navigate, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { toast, Toaster } from '@redwoodjs/web/toast'

const WELCOME_MESSAGE = 'Welcome back!'
const REDIRECT = routes.home()

const LoginPage = ({ type }) => {
  const {
    isAuthenticated,
    client: webAuthn,
    loading,
    logIn,
    reauthenticate,
  } = useAuth()
  const [shouldShowWebAuthn, setShouldShowWebAuthn] = useState(false)
  const [showWebAuthn, setShowWebAuthn] = useState(
    webAuthn.isEnabled() && type !== 'password'
  )

  // should redirect right after login or wait to show the webAuthn prompts?
  useEffect(() => {
    if (isAuthenticated && (!shouldShowWebAuthn || webAuthn.isEnabled())) {
      navigate(REDIRECT)
    }
  }, [isAuthenticated, shouldShowWebAuthn])

  // if WebAuthn is enabled, show the prompt as soon as the page loads
  useEffect(() => {
    if (!loading && !isAuthenticated && showWebAuthn) {
      onAuthenticate()
    }
  }, [loading, isAuthenticated])

  // focus on the username field as soon as the page loads
  const usernameRef = useRef()
  useEffect(() => {
    usernameRef.current && usernameRef.current.focus()
  }, [])

  const onSubmit = async (data) => {
    const webAuthnSupported = await webAuthn.isSupported()

    if (webAuthnSupported) {
      setShouldShowWebAuthn(true)
    }
    const response = await logIn({ ...data })

    if (response.message) {
      // auth details good, but user not logged in
      toast(response.message)
    } else if (response.error) {
      // error while authenticating
      toast.error(response.error)
    } else {
      // user logged in
      if (webAuthnSupported) {
        setShowWebAuthn(true)
      } else {
        toast.success(WELCOME_MESSAGE)
      }
    }
  }

  const onAuthenticate = async () => {
    try {
      await webAuthn.authenticate()
      await reauthenticate()
      toast.success(WELCOME_MESSAGE)
      navigate(REDIRECT)
    } catch (e) {
      if (e.name === 'WebAuthnDeviceNotFoundError') {
        toast.error(
          'Device not found, log in with username/password to continue'
        )
        setShowWebAuthn(false)
      } else {
        toast.error(e.message)
      }
    }
  }

  const onRegister = async () => {
    try {
      await webAuthn.register()
      toast.success(WELCOME_MESSAGE)
      navigate(REDIRECT)
    } catch (e) {
      toast.error(e.message)
    }
  }

  const onSkip = () => {
    toast.success(WELCOME_MESSAGE)
    setShouldShowWebAuthn(false)
  }

  const AuthWebAuthnPrompt = () => {
    return (
      <div className="rw-webauthn-wrapper">
        <h2>WebAuthn Login Enabled</h2>
        <p>Log in with your fingerprint, face or PIN</p>
        <div className="rw-button-group">
          <button className="rw-button rw-button-blue" onClick={onAuthenticate}>
            Open Authenticator
          </button>
        </div>
      </div>
    )
  }

  const RegisterWebAuthnPrompt = () => (
    <div className="rw-webauthn-wrapper">
      <h2>No more passwords!</h2>
      <p>
        Depending on your device you can log in with your fingerprint, face or
        PIN next time.
      </p>
      <div className="rw-button-group">
        <button className="rw-button rw-button-blue" onClick={onRegister}>
          Turn On
        </button>
        <button className="rw-button" onClick={onSkip}>
          Skip for now
        </button>
      </div>
    </div>
  )

  const PasswordForm = () => (
    <Form onSubmit={onSubmit} className="rw-form-wrapper">
      <Label
        name="username"
        className="rw-label"
        errorClassName="rw-label rw-label-error"
      >
        Username
      </Label>
      <TextField
        name="username"
        className="rw-input"
        errorClassName="rw-input rw-input-error"
        ref={usernameRef}
        autoFocus
        validation={{
          required: {
            value: true,
            message: 'Username is required',
          },
        }}
      />

      <FieldError name="username" className="rw-field-error" />

      <Label
        name="password"
        className="rw-label"
        errorClassName="rw-label rw-label-error"
      >
        Password
      </Label>
      <PasswordField
        name="password"
        className="rw-input"
        errorClassName="rw-input rw-input-error"
        autoComplete="current-password"
        validation={{
          required: {
            value: true,
            message: 'Password is required',
          },
        }}
      />

      <div className="rw-forgot-link">
        <Link to={routes.forgotPassword()} className="rw-forgot-link">
          Forgot Password?
        </Link>
      </div>

      <FieldError name="password" className="rw-field-error" />

      <div className="rw-button-group">
        <Submit className="rw-button rw-button-blue">Login</Submit>
      </div>
    </Form>
  )

  const formToRender = () => {
    if (showWebAuthn) {
      if (webAuthn.isEnabled()) {
        return <AuthWebAuthnPrompt />
      } else {
        return <RegisterWebAuthnPrompt />
      }
    } else {
      return <PasswordForm />
    }
  }

  const linkToRender = () => {
    if (showWebAuthn) {
      if (webAuthn.isEnabled()) {
        return (
          <div className="rw-login-link">
            <span>or login with </span>{' '}
            <a href="?type=password" className="rw-link">
              username and password
            </a>
          </div>
        )
      }
    } else {
      return (
        <div className="rw-login-link">
          <span>Don&apos;t have an account?</span>{' '}
          <Link to={routes.signup()} className="rw-link">
            Sign up!
          </Link>
        </div>
      )
    }
  }

  if (loading) {
    return null
  }

  return (
    <>
      <MetaTags title="Login" />

      <main className="rw-main">
        <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
        <div className="rw-scaffold rw-login-container">
          <div className="rw-segment">
            <header className="rw-segment-header">
              <h2 className="rw-heading rw-heading-secondary">Login</h2>
            </header>

            <div className="rw-segment-main">
              <div className="rw-form-wrapper">{formToRender()}</div>
            </div>
          </div>
          {linkToRender()}
        </div>
      </main>
    </>
  )
}

export default LoginPage
1 Like

I’m having this issue as well. Any updates on this or am I missing something?

If your using a recent version of redwood, which defaults to vite, you can’t be using require to import stuff anymore. It might even be a dependency triggering this.

I was able to fix my issue by making sure that I’m not importing anything from ‘@redwoodjs/auth-dbauth-web’ or ‘@redwoodjs/auth-dbauth-web/webAuthn’ during prerender using the isBrowser helpers and React lazy loading. This included my useAuth, App, Routes, and every page I’m prerendering. I will have to be careful to not call my ‘useAuth’ hook in any component that will be pre-rendered.

@Gamesearch56 if you don’t need the auth pages pre-rendered I would recommend trying this out.