Redwood v5.0.0 Upgrade Guide

Before we get started, just in case you missed them, here are quick links to the announcement post and Changelog:

First run the upgrade command, then we’ll go through all the breaking changes one by one:

yarn rw upgrade

:point_up: Heads up

If you don’t have a clean working directory, commit or stash your changes before running codemods as they can overwrite files!

Breaking changes

Precautions

React 18 Changes

React 18

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary upgrade-to-react-18

Most of the breaking changes introduced by React 18 were internal to the framework, but you may encounter behavioral changes that are breaking in your pages and components. For example, something that rendered twice before now may render only once. Or, a component or library you’re using doesn’t support React 18 yet. If you encounter anything, please share it with us here! At the end of this upgrade guide is a running list of some of the changes users have encountered: Redwood v5.0.0 Upgrade Guide.

As far as upgrading, there’s only two guaranteed changes you’ll have to make. If you have a custom web index, there’s a third change. But regardless of your setup, the codemod should take care of these changes for you:

  1. Change the versions of React in your web side’s package.json to v18.2.0:
// web/package.json

 dependencies: {
-  "react": "18.2.0",
+  "react": "18.2.0",
-  "react-dom": "18.2.0"
+  "react-dom": "18.2.0"
 ...
  1. Update your project’s web/src/index.html file by removing the prerenderPlaceholder template syntax:
<!-- web/src/index.html -->

...

 <body>
   <div id="redwood-app">
-    <!-- Please keep the line below for prerender support. -->
-    <%= prerenderPlaceholder %>
   </div>
 </body>

 </html>

If you added other HTML elements to the react root, (<div id="redwood-app"></div>) you’ll need to move them elsewhere (probably into a layout), or else you’ll encounter hydration errors while prerendering. React expects to have full control of this DOM node.

  1. If you’ve used the yarn rw setup custom-web-index command to set up your own web entry point, or use Vite, you’ll have to make the changes to how React 18 wants to mount to the DOM yourself, but you can refer to the new default template here: https://github.com/redwoodjs/redwood/blob/710c316c4272222293c49001881024774b8b9fcb/packages/web/src/entry/index.js. Just replace import App from '~redwood-app-root' with import App from './App' and you should be good to go.

Besides upgrading React, we’ve also upgraded its TypeScript types and React Testing Library. For TypeScript and React Testing Library users (that is, you’re writing your tests in TypeScript), there’s a small-but-potentially-annoying type change to the generics in the renderHook function. If you’re consuming its return type (e.g. doing something like ReturnType<typeof renderHook>), see the change we had to make here: feat(react): Upgrade to React 18 by virtuoushub · Pull Request #4992 · redwoodjs/redwood · GitHub

Most of React’s upgrade guide won’t apply to you, but there are certain sections that are worth reading, like Automatic Batching and Other Breaking Changes. It’s also worth skimming their new docs.

PRs:

RedwoodJS packages now target Node.js v18.16

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary update-node-engine-to-18

All Redwood’s deploy targets now support deploying with Node.js 18 LTS, so we’ve bumped the version of Node.js that Redwood packages target to v18.16. The codemod above updates the node version in the engines field in the root package.json.

After upgrading your project to v5, please update the version of Node.js your deploy provider uses to 18. Refer to your deploy provider’s relevant documentation. It varies from selecting a different version from a dropdown in a UI to changing the value in an .nvmrc file.

PR: Bump the Node.js build target to 18 LTS by jtoar · Pull Request #8106 · redwoodjs/redwood · GitHub

TypeScript v5

TypeScript v5 was released in March and we were able to include it in this major because there was surprisingly little we had to do to upgrade to it. We were actually able to remove some of the @ts-expect-error directives we had because they were no longer errors.

We don’t have any explicit changes for you to make. It’ll vary based on your use of TypeScript. Have a look at the announcement here and consider if any of the breaking changes affects you. If you use enums, pay particular attention to the new way they behave:

PR: chore(deps): update dependency typescript to v5 by renovate[bot] · Pull Request #7845 · redwoodjs/redwood · GitHub

In Cells, GraphQL-client related props are now in an object named queryResult

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary cell-query-result

The components in a Cell (Failure, Loading, Empty, and Success) have access to a lot of props. They have access to basically everything your GraphQL client’s useQuery hook returns. In Apollo Client’s case, it’s a lot. So there’s a non-zero chance that one of its props overwrites one of your data props. By far the most common case is if you have a model named “client” it’ll be overwritten by Apollo Client’s “client” prop (which gives you direct access to the GraphQL client).

This has come up often enough that we’ve decided to stop spreading the GraphQL-client related props that useQuery returns. Instead, they’re all accessible via an object named queryResult.

This one was a bit trickier for us to codemod, and while we’ve added a lot of tests, do let us know if the codemod doesn’t work for you!

PR: fix: avoid naming conflict with `client` prop by esteban-url · Pull Request #7024 · redwoodjs/redwood · GitHub

Cells check both query fields for data

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary detect-empty-cells

Note that this codemod doesn’t actually change your code, but warns you about Cells who’s behavior may have changed.

Cells with more than one field on the GraphQL query type behave somewhat counterintuitively:

export const QUERY = gql`
  users {
    name
  }
  posts {
    title
  }
`

If users doesn’t return any data, but posts does, the Cell shows Empty instead of Success. This probably isn’t what you expect, so we’ve fixed it in this release to show Success if there’s any data, not only if there’s data in the first field on the query type.

The codemod above scans for Cells in your project that are affected by this change, and alerts you to them. If the new behavior isn’t what you want, consider exporting your own isEmpty function from the Cell that only checks the first field.

PR: [Cells]: Show Success if there's any data in any of the fields by jtoar · Pull Request #7704 · redwoodjs/redwood · GitHub

validateWith is now validateWithSync

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary rename-validate-with

We’ve decided to make the validateWith function from @redwoodjs/api take an asynchronous function instead of a synchronous one. Following Node’s convention, we’ve added a synchronous version of validateWith, validateWithSync. This means that all calls to validateWith need to be changed to validateWithSync. While you could do a search and replace, The codemod should take care of that for you.

PR: Makes validateWith async and a new validateWithSync synchronous by cannikin · Pull Request #7681 · redwoodjs/redwood · GitHub

Chakra UI v2

Now that Redwood is on React 18, you can upgrade to Chakra UI v2. The upgrade process will depend on your use of Chakra, but at the very least you’ll have to update the @chakra-ui/react and framer-motion packages in your web side’s package.json:

// web/package.json

 dependencies: {
-    '@chakra-ui/react@^1',
+    '@chakra-ui/react@^2',
-    'framer-motion@^8',
+    'framer-motion@^9',
 ...

See Chakra’s upgrade guide for more:

PR: chore(deps): bump setup of Chakra UI to V2 by virtuoushub · Pull Request #7649 · redwoodjs/redwood · GitHub

More types for auth methods

We’ve continued improving the types for all of Redwood’s auth integrations. While there’s nothing breaking here in terms of behavior, the TypeScript changes we’ve made for correctness may introduce type errors, which may fail your build.

PR: Auth: Add TS types for auth method options by Tobbe · Pull Request #7745 · redwoodjs/redwood · GitHub

Auth0 v2

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary update-auth0-to-v2

Auth0 has released v2 of their SPA SDKs. There’s a simple breaking change to the way the SDK is instantiated in web/src/auth.tsx that we’ve provided a codemod for, but there’s more behavioral breaking changes that may affect you depending on your use of their SDK. Refer to their migration guide: auth0-spa-js/MIGRATION_GUIDE.md at main · auth0/auth0-spa-js · GitHub.

PR: chore(deps): update dependency @auth0/auth0-spa-js to v2 by renovate[bot] · Pull Request #7524 · redwoodjs/redwood · GitHub

Supabase v2

Supabase released v2 of their JS SDK some time ago—we’ve finally upgraded to it in this release! The key changes are improved TypeScript support and new authentication methods. We’ll walk through the breaking changes here, but for all the details (features, fixes, etc.), check out these links:

The major breaking change in v2 is that Supabase removed their SDK’s catch-all signIn() method in favor of more-explicit method signatures like signInWithPassword() and signInWithOtp(). So the logIn method from Redwood’s useAuth hook now needs to know which authentication strategy you want to use via a new prop, authMethod. Here’s an example:

// In a React component...

const { logIn } = useAuth()

// Log in using an email and a password
await logIn({
  authMethod: 'password',
  email: 'example@email.com',
  password: 'example-password',
})

// Or, log in using a one time password (OTP)
await logIn({
  authMethod: 'otp',
  email: 'example@email.com',
  options: {
    emailRedirectTo: 'https://example.com/welcome'
  }
}) 

Lastly, some client-specific methods can no longer be accessed from useAuth() directly, such as verifyOtp, onAuthStateChange, and getSession. To use these methods, you’ll have to access the client directly. Here’s an example:

// In a React component...

const { client } = useAuth()

useEffect(() => {
  const { data, error } = await client.getSession()
}, [client])

Check out the new RedwoodJS docs for more examples of how to use the new SDK:

PR: feature: Upgrade Supabase Auth to v2 by dthyresson · Pull Request #7719 · redwoodjs/redwood · GitHub

SimpleWebAuthn v7

We upgraded the SimpleWebAuthn packages from v6 to v7. The upgrade was fairly simple; it mainly involved adding Buffer.from to a few places in the code to change SimpleWebAuthn’s new platform-agnostic API back to a Node.js one.

To upgrade, simply update the versions of the @simplewebauthn/* packages in your package.jsons:

// api package.json

 dependencies: {
-    "@simplewebauthn/server": "^6"
+    "@simplewebauthn/server": "^7"
 ...

// web package.json

 dependencies: {
-    "@simplewebauthn/browser": "^6",
+    "@simplewebauthn/browser": "^7",
 ...

You can refer to the full release notes here:

PR: Upgrade simplewebauthn packages by jtoar · Pull Request #7477 · redwoodjs/redwood · GitHub

Deprecating support for the Serverless Framework

We’re deprecating support for the Serverless Framework. See this topic for details:

Different jest.restoreAllMocks() behavior

Precaution

This most likely isn’t a breaking change for you; we’re just listing it as a precaution. It’s also something you may have already encountered in v4.5.0 due to upstream dependencies.

Jest v29.4.3 fixed a bug in jest.restoreAllMocks(), but we had to change our code to accommodate upgrading to this version. That means you could too. If you have tests that use jest.restoreAllMocks() and they’re failing after upgrading to v5, consider changing jest.restoreAllMocks() to jest.resetAllMocks() like we did here:

Small @apollo/client type change

Precaution

This most likely isn’t a breaking change for you; we’re just listing it as a precaution.

Apollo Client made a TypeScript change for the better, but because the change uses the extends keyword to enforce more restrictions on a generic, there’s a chance it’ll break type checking in your project. It’s fairly unlikely that it will because in practice, you couldn’t pass anything you wanted to the useQuery hook, but for good measure, here’s the change we had to make to the framework, and the new OperationVariables type (it’s just a Record):

MSW (Mock Service Worker) v1

Precaution

This most likely isn’t a breaking change for you; we’re just listing it as a precaution.

The breaking change was internal to @redwoodjs/testing, and it only broke type checking. But we figured there’s a chance you’re using MSW more directly in your project. If you are, have a look at the type change in v1 here. It’s a straightforward rename:

dbAuth security improvements to resetToken

Precaution

This most likely isn’t a breaking change for you; we’re just listing it as a precaution.

v5 brings a major security improvement to dbAuth: it no longer stores raw resetTokens in the database and instead stores the sha256 hash of the token. This makes it so that if a database is compromised, an attacker can’t steal resetTokens and reset users’ passwords.

We’re listing this as a precaution for two reasons:

  • If you refetch the User object in the dbauth handler, this may be a breaking change for you because this new functionality relies on mutating the User object. We believe that it’s rather unlikely that you’re doing this, but if you happen to be, reach out to us here or on GitHub and we’ll see if we can design a better pattern.
  • If you upgrade to v5 while password resets are in-transit, they could be in limbo due to the difference in the way they’re handled between v4 and v5. For low-to-medium traffic sites, this probably isn’t a problem. (Maybe just have an error message that asks users to try again if the token doesn’t match.)
    For high-traffic sites, or for the best end-user experience, you can temporarily disable password resets while upgrading. Just query your database and wait until no more valid reset tokens exist (by looking at resetTokenExpiresAt), then upgrade and re-enable password resets.

PR: feat: Change to using resetToken hash in DB by jaiakt · Pull Request #8041 · redwoodjs/redwood · GitHub

The logger now prints metadata locally

Precaution

This most likely isn’t a breaking change for you; we’re just listing it as a precaution.

In v4 and before, the logger behaved somewhat differently between development and production. In development, if you logged metadata, like logger.debug({ user }, 'my user') (where the first argument, { user } is the metadata), only the second argument and on, “my user” here, would be printed to the terminal. Now the full user object will be printed too, making it more inline with what happens in production.

PR: chore: Improve LogFormatter to include Types and Support Additional Options without the Custom attribute by dthyresson · Pull Request #7813 · redwoodjs/redwood · GitHub

React 18 Changes

Here’s a running list of some gotchas users have encountered with React 18.

@apollo/client useMutation result

The useMutation hook from @apollo/client returns a tuple that has a mutate function and an object representing the mutation result:

const [mutate, mutationResult] = useMutation(MUTATION)

In version v4.x of Redwood, you could depend on mutationResult to be updated in useMutation’s onCompleted callback:

const [mutate, mutationResult] = useMutation(MUTATION, {
  onCompleted: () => {
    if (mutationResult.data.myDataProperty === null) {
      // do something
    }
  },
})

But now, in v5 with React 18, mutationResult doesn’t seem to update in this callback. Instead, opt for the data prop from onCompleted:

const [mutate, mutationResult] = useMutation(MUTATION, {
  onCompleted: (data) => {
    if (data.myDataProperty === null) {
      // do something
    }
  },
})

Thanks to @razzeee for reporting this!

8 Likes

Hi,
looking forward to the release!
I am already trying it out with version rc-625. I am getting the following error on a web side build: ReferenceError: __webpack_require__ is not defined my compiled index.js contains entries like this:

ybi={name:"ForgotPasswordPage",loader:()=>_s(()=>import("./ForgotPasswordPage-3ee7e0a4.js"),[]),prerenderLoader:()=>__webpack_require__(require.resolveWeak("./pages/ForgotPasswordPage/ForgotPasswordPage"))}

i am not using prerender and webpack though. It leads to a Something went wrong page. So I cannot access my site.
I took out the prerender part from index.html as described above. In earlier versions the webpack_require is not part of my compiled files.

I think its an issue with custom babel plugin. I mention it at end of: https://github.com/redwoodjs/redwood/pull/7760

Edit: It seems the error is not appearing anymore when removing the prerender comment from index.html, but compiled files still contain webpack_require. I don´t have any prerendered pages, so I am not sure, if this creates some issues, later on. At least I can open my site now…

1 Like

Hey @dennemark, thanks for trying out the RC!

That means during yarn rw build or yarn rw build web right?

Ah I see—you must be using Vite then? Upgrading to React 18 involved making some changes to how we bundle prerendered pages. They need to be there with the main bundle so that we don’t get any hydration warnings. The server HTML must match the client-side rendered HTML, at least for the initial render.

We made these bundling changes with webpack but not with Vite. We may be able to work on porting the changes over to Vite during this RC period but I can’t promise it just yet. But I wouldn’t expect there to be any prerender logic there if the page isn’t prerendered. I’ll take a closer look—can I confirm what you’re seeing is webpack logic in built files even though your project uses Vite as a bundler and no pages are marked as prerender?

@dom Yes I can confirm that the webpack logic is compiled into built files when using vite/rollup even though I do not use prerender. Got it to work though as described above by removing the prerender part from index.html. I think @Tobbe will have a look at it.

Thanks for the ping @dennemark, I’ll definitely look at the vite prerender issue. Having a bit of a hard time deciding how to split my (limited) time between SSR/Streaming (which uses Vite) and other RW tasks/projects, but I’ll try to at least take a quick look at the issue this weekend or early next week

I’ve created an issue for the Vite issue

Is the first poste missing the part, where we actually update the redwood version, or am I overlooking that?

Yeah I overlooked that—let me add it, thanks!

Only thing I can see, that seems broken in one of our projects is a table with @tanstack/react-table that you can expand and that then loads more data via a cell.

Firefox hangs and then just allows me to kill the execution after some time.

Hi - first time poster here so please let me know if I can provide any more info. I just upgraded my fairly simple app to version 5.0.0 using Supabase for auth + db and I’m able to get everything working perfectly locally.

Upon deploying to Vercel, I’m getting errors on my GraphQL requests. Here is a copy of the log –

…{"level":50,"time":1682637348241,"pid":8,"hostname":"169.254.56.21","name":"graphql-server","err":{"type":"GraphQLError","message":"Could not find a \"redwood.toml\" file, are you sure you're in a Redwood project?","stack":"Error: Could not find a \"redwood.toml\" file, are you sure you're in a Redwood project?\n    at getConfigPath (/var/task/node_modules/@redwoodjs/project-config/dist/configPath.js:20:11)\n    at getConfig (/var/task/node_modules/@redwoodjs/project-config/dist/config.js:91:63)\n    at Object.latestPacks (/var/task/node_modules/@redwoodjs/graphql-server/dist/makeMergedSchema/makeMergedSchema.js:69:44)\n    at useRedwoodDirectiveValidatorResolver (/var/task/node_modules/@redwoodjs/graphql-server/dist/plugins/useRedwoodDirective.js:108:22)\n    at field.resolve (/var/task/node_modules/@envelop/on-resolve/cjs/index.js:33:48)\n    at async field.resolve (/var/task/node_modules/@envelop/on-resolve/cjs/index.js:33:42)\n    at async field.resolve (/var/task/node_modules/@envelop/on-resolve/cjs/index.js:33:42)\n    at async Promise.all (index 0)\n    at async promiseForObject (/var/task/node_modules/@graphql-tools/executor/cjs/execution/promiseForObject.js:14:28)\n    at async /var/task/node_modules/@envelop/core/cjs/orchestrator.js:376:27","path":["latestPacks"],"locations":[{"line":2,"column":3}],"extensions":{}},"msg":"Could not find a \"redwood.toml\" file, are you sure you're in a Redwood project?"}
{"level":50,"time":1682637348241,"pid":8,"hostname":"169.254.56.21","err":{"type":"GraphQLError","message":"Could not find a \"redwood.toml\" file, are you sure you're in a Redwood project?","stack":"Error: Could not find a \"redwood.toml\" file, are you sure you're in a Redwood project?\n    at getConfigPath (/var/task/node_modules/@redwoodjs/project-config/dist/configPath.js:20:11)\n    at getConfig (/var/task/node_modules/@redwoodjs/project-config/dist/config.js:91:63)\n    at Object.latestPacks (/var/task/node_modules/@redwoodjs/graphql-server/dist/makeMergedSchema/makeMergedSchema.js:69:44)\n    at useRedwoodDirectiveValidatorResolver (/var/task/node_modules/@redwoodjs/graphql-server/dist/plugins/useRedwoodDirective.js:108:22)\n    at field.resolve (/var/task/node_modules/@envelop/on-resolve/cjs/index.js:33:48)\n    at async field.resolve (/var/task/node_modules/@envelop/on-resolve/cjs/index.js:33:42)\n    at async field.resolve (/var/task/node_modules/@envelop/on-resolve/cjs/index.js:33:42)\n    at async Promise.all (index 0)\n    at async promiseForObject (/var/task/node_modules/@graphql-tools/executor/cjs/execution/promiseForObject.js:14:28)\n    at async /var/task/node_modules/@envelop/core/cjs/orchestrator.js:376:27","path":["latestPacks"],"locations":[{"line":2,"column":3}],"extensions":{}},"msg":"Could not find a \"redwood.toml\" file, are you sure you're in a Redwood project?"}

Could redwood.toml be missing from the build dist?

3 Likes

Hey @jmdesiderio, I just found this error as well and have a fix for it here in main already

I’m running a deploy test right now to see if it works. If it does I’ll push out a patch shortly. Sorry about that!

3 Likes

Amazing – so quick! Appreciate everything ya’ll are doing!

Any time, appreciate you using Redwood! Patch is out, worked for me in my deploys to Netlify and Vercel but do let me know if it works on your end too:

1 Like

Just to follow up on this one (@tanstack/react-table) publicly, @razzeee and I had a quick meeting so I could see the issue. I can’t reproduce it locally yet, but am trying to—if you run into it as well please chime in and if you could supply a reproduction that’d be invaluable

It seems to be solveable by using useEffect to set the variables. Before the version bump I was able to get away with just assigning it.

I still need to see if that also fixes my 2nd app, but I’m pretty confident.

1 Like

With version 4.0.0 I could do


  const [deleteX, deleteResult] =
    useMutation<DeleteXMutation>(DELETE_X_MUTATION, {
      onCompleted: () => {
        toast.success(t('x-deleted'))

        // happens when the last satisfaction is deleted
        if (deleteResult.data.deleteX === null) {
          setIsModalOpen(false)
        }
      },
    })

and deleteResult seemed to always have loaded in onComplete

in my v5 test branches, it seems that’s not the case anymore, but using data from onCompleted seems to be fine


  const [deleteX] =
    useMutation<DeleteXMutation>(DELETE_X_MUTATION, {
      onCompleted: (data) => {
        toast.success(t('x-deleted'))

        // happens when the last satisfaction is deleted
        if (data.deleteX === null) {
          setIsModalOpen(false)
        }
      },
    })

not sure, if this also negativly affects the other fields that can be returned there.

@razzeee I can reproduce this, and I’m looking through the @apollo/client changelog now (we upgraded from v3.7.5 to v3.7.12 from RedwoodJS v4.5.0 to v5.0.0).

I’m actually a little confused how using deleteResult, a return value from useMutation, in the call to useMutation itself is possible. Isn’t that a little like using a variable before it’s defined? I don’t get a linting error in my project so there must be something I don’t quite understand about it yet. Maybe it’s ok cause it’s just closing over an object.

Regardless, it does seem to work the way you described in v4 and now has different behavior in v5 so I’ll note it in the upgrade guide and keep looking for what changed internally in @apollo/client.

Now I’m speculating that it’s not a change in @apollo/client, but a change in React. Maybe React is being more strict about the snapshots it takes. Regardless, thanks for reporting as always!

1 Like

My best guess is, that react is more parallel now and thus it’s not guaranteed anymore, that the request has finished, when you are in the onCompleted. Not sure that makes much sense, but this is a guess at best.

1 Like

Maybe you could try something like this to verify

  const [deleteX, deleteResult] =
    useMutation<DeleteXMutation>(DELETE_X_MUTATION, {
      onCompleted: () => {
        toast.success(t('x-deleted'))

        setTimeout(() => {
          // happens when the last satisfaction is deleted
          if (deleteResult.data.deleteX === null) {
            setIsModalOpen(false)
          }
        }, 1000)
      },
    })