Redwood v6.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—they can overwrite files!

Breaking changes


In Redwood v6, all projects use Vite by default.

if you’re not ready to migrate to Vite yet, you can switch back to Webpack via the bundler key in your project’s redwood.toml file:

+ bundler = "webpack" # 👈 New bundler key
  port = 8910
  apiUrl = "/api"
  includeEnvironmentVariables = ["CONTEXT", "NODE_ENV", "DEPLOY_ID"]
  title = "My Redwood App"

Even if you switch back to Webpack, we still recommend running through all the changes here. We plan to remove Webpack support in a future major, but won’t do so till we see strong adoption metrics.

Without further ado, let’s set up Vite:

yarn rw setup vite

:information_source: Wait—are you an existing Vite user?

If you opted into Redwood’s experimental Vite support in the last couple releases (thank you!), you’ll have to make these changes instead of setting up Vite again:

  1. The entry point is now entry.client.{jsx,tsx} instead of entry-client.{jsx/tsx}
  2. @redwoodjs/vite is a dev dependency, instead of a dependency . Take a look at the package.json in the template for reference

Be sure to follow the rest of the guide!

This command

  • adds the entry point, web/src/entry.client.{jsx,tsx}
  • adds Vite’s config file, web/vite.config.{js,ts}
  • adds @redwoodjs/vite as a dev dependency to the web workspace

Here’s a short description of the new files:

File Description
web/src/entry.client.{jsx,tsx} Your project’s entry point. This file was previously hidden; it’s where you’ll see React mount to the DOM. Vite starts bundling from here.
web/vite.config.{js,ts} Vite’s config file. Redwood’s config is just a Vite plugin.

1. Update the DevFatalErrorPage

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods update-dev-fatal-error-page

:construction_worker_man: Heads up

This will replace your FatalErrorPage. If you’ve customized it, you may want to do this manually. Keep reading, it’s easy!

With Vite, require statements are no longer supported. You’ll need to replace them with import or await import statements.

Most Redwood apps have at least one require statement, in web/src/pages/FatalErrorPage.{js,tsx}:

// web/src/pages/FatalErrorPage.{js,tsx}

// ...

// Ensures that production builds do not include the error page
let RedwoodDevFatalErrorPage = undefined
if (process.env.NODE_ENV === 'development') {
  RedwoodDevFatalErrorPage =

This won’t work anymore, so either run the codemod above or replace the require statement:

// web/src/pages/FatalErrorPage.{js,tsx}

// ...

- let RedwoodDevFatalErrorPage = undefined
- if (process.env.NODE_ENV === 'development') {
-   RedwoodDevFatalErrorPage =
-     require('@redwoodjs/web/dist/components/DevFatalErrorPage').DevFatalErrorPage
- }
+ import { DevFatalErrorPage } from '@redwoodjs/web/dist/components/DevFatalErrorPage'

- export default RedwoodDevFatalErrorPage ||
+ export default DevFatalErrorPage ||
     (() => (

2. Change all files using JSX with the .js extension to .jsx

:information_source: Using TypeScript?

If your project is 100% TypeScript, you can probably skip this step—.tsx files remain unchanged.

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods convert-js-to-jsx

We’re deprecating the use of JSX syntax in .js files. While files with the .js extension will still work, you’ll be missing out on features like fast refresh.

If you have a JS file that doesn’t have JSX, its extension doesn’t need to change. Only those with JSX do.

3. Changes to how process.env is used

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods process-env-dot-notation

In v6, there are differences in the way environment variables are loaded:

  1. If you’re receiving an error in the browser like "process is undefined", it means that you…

    a. haven’t included the environment variable in your project’s redwood.toml file

    b. haven’t prefixed it with REDWOOD_ENV_

    c. just don’t have it defined in one of your .env files at all

# redwood.toml

  bundler = "vite"
  includeEnvironmentVariables = [
    "BAZINGA", # 👈 Make sure it's included here
  1. Access process.env with dot notation

You can no longer access environment variables using array syntax, like process.env['MY_ENV_VAR']. To fix this, use dot notation:

- process.env['MY_ENV_VAR']
+ process.env.MY_ENV_VAR

4. Using SVGs as components

If you’re importing directly from SVG files and using them as components, you’ll need to update your code in one of a few ways (detailed below)—we’re dropping support for it in this release.

By importing directly from SVG files, we mean like this:

// web/src/components/MyComponent/MyComponent.jsx

import Icon from './icon.svg' // 👈 Directly imported from the SVG file

const MyComponent = () => {
  return (
      <Icon/> {/* 👈 Used as a component here */}

You have a couple options:

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods replace-component-svgs
  • if you are not styling your SVGs directly, you can also use an <img> tag:
import icon from './myIcon.svg'

const MyComponent = () => {
  return (
      <img src={icon} alt="..."/> {/* 👈 Pass the url to the img's src attribute */}
  • add the SVGR plugin: vite-plugin-svgr - npm (SVGR was the plugin we used with Webpack to make these kind of imports work)

5. Use globalThis instead of global

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods change-global-to-global-this

Webpack polyfills the legacy Node.js global object. As it’s not part of the ECMAScript language specification, Vite doesn’t, which means all instances of global have to be changed to globalThis.

6. Static assets in the public folder are now served from / instead of /public

With Webpack, you probably referred to static assets in the web/public folder using URIs starting with /public/...:

/* web/src/index.css */

@font-face {
  font-family: 'My font';
  src: url('/public/fonts/my-font.woff2') format('woff2'); /* 👈 URI starts with "/public" */

Vite serves them at / instead. Just remove /public from the URIs and everything should work again:

/* web/src/index.css */

@font-face {
  font-family: 'My font';
- src: url('/public/fonts/my-font.woff2') format('woff2');
+ src: url('/fonts/my-font.woff2') format('woff2');

7. Update Mantine and Chakra UI config

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods update-theme-config

If you’re using Mantine or Chakra UI, you’ll need to update how these libraries are configured. You may see an error like this in your browser’s console:

Uncaught ReferenceError: module is not defined

To fix this, you’ll need to make sure you’re not using module.exports in your web/config/mantine.config.{js,ts} or web/config/chakra.config.{js,ts} files. The change is simple:

// web/config/{mantine,chakra}.config.{js,ts}

- module.exports = // Your theme...
+ const theme = // Your theme...
+ export default theme

8. Web workers require type: "module"

Vite outputs ESM. Depending on the web APIs you’re using, you may have to make some changes to configure them to work with ESM. One example is Web Workers. Web Workers default to the "classic" type, which don’t understand ESM syntax like import.

Usually the change is as simple as adding something like type: "module" to the API’s configuration object:

// web/src/pages/WebWorkerPage/WebWorkerPage.tsx

 const WebWorkerPage = () => {
-  const myWorker = new Worker("worker.js");
+  const myWorker = new Worker("worker.js", { type: "module" });
   // ...

Prisma v5

This major version of Redwood includes Prisma v5. Going along with the theme of this release, Prisma v5 has many changes that improve its performance, especially in serverless environments.

The breaking changes in Prisma v5 are relatively niche; for completeness, see Prisma’s upgrade guide linked below, but we don’t anticipate you having to make many changes if any are necessary at all:

Storybook v7

:information_source: Storybook still uses the Webpack builder internally

As of v6.0.0, even if your project uses Vite, Storybook still uses the Webpack builder. We plan to ship the Vite builder for Storybook shortly, in a future minor, but couldn’t get it working in time for this release.

But the thing to watch out for here is that if you customize your project’s Vite config, it won’t apply to Storybook.

In this major, we’ve upgraded to Storybook v7 and decoupled it from the framework.

Most of the breaking changes in Storybook v7 were internal to the framework, but depending on the extent to which you use Storybook and its community addons, your results may vary. Here’s what we know for sure:

  • Storybook removed the ability to configure the manager, which is the UI. If you were configuring the manager via the web/config/storybook.manager.js file, you’ll have to delete the file—Storybook won’t load it anymore.
  • If you use MDX stories, you’ll have to upgrade them to MDX v2. Refer to Storybook’s migration guide here: Migration guide for Storybook 7.0.
  • For TypeScript projects, the ComponentMeta type from @storybook/react is deprecated in favor of the Meta type. The csf-2-to-3 codemod below updates it for you.

While it’s optional, we recommend upgrading your stories to Component Story Format 3. It’s fully backwards compatible and Storybook includes a codemod for it:

npx storybook@next migrate csf-2-to-3 --glob="web/**/*.stories.{js,tsx}"

One thing you’ll notice when you start Storybook for the first time after upgrading to Redwood v6 is that the Redwood CLI will install the @redwoodjs/cli-storybook package:

$ yarn redwood storybook
Installing plugin "@redwoodjs/cli-storybook"...

Storybook is quite large and not everyone who uses Redwood uses it, so we’ve decoupled it from the CLI and made it a plugin so that it installs lazily, the first time you use it. We intend to do this with more commands to make Redwood leaner out of the box.

Suspense Router

We refactored the Router to use Suspense under the hood. For most, this refactor will largely go unnoticed. But there’s a subtle breaking change: components that aren’t Router components (Route, Set, and Private) won’t be rendered anymore.

The only place you could’ve put a component like this was in a Set, like this:

// web/src/Routes.tsx


const Routes = () => {
  return (
        <MyCustomReactProvider>  {/* 👈 This provider should be in the `Set`'s `wrap` prop */}
          <Route path="/my-route" page={MyPage} name="myPage" />
      <Route path="/" page={HomePage} name="home" />
      <Route notfound page={NotFoundPage} />

The fact that you could do this wasn’t intentional and was an artifact of the way the Router was written. Redwood always meant for these kinds of components to be in the Set’s wrap prop, like this:

-      <Set>
-        <MyCustomReactProvider>  {/* 👈 This provider should be in the `Set`'s `wrap` prop */}
+      <Set wrap={MyCustomReactProvider}>

We added an ESLint rule in v5.4.0 to help you identify this problem. In this major, that rule will error instead of warn:

Having linting issues? If you haven’t yet, try restarting the ESLint server. If you’re using VS Code, you can do this by opening the Command Palette and typing in “ESLint: Restart ESLint Server”.

RedwoodApolloProvider’s link prop

:information_source: Are you customizing RedwoodApolloProvider’s Apollo Link?

If you don’t customize RedwoodApolloProvider's Apollo Link at all, you can skip this step.

The RedwoodApolloProvider in web/src/App.{jsx,tsx} takes a prop called graphQLClientConfig:

import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'

// ...

const App = () => (
  <FatalErrorBoundary page={FatalErrorPage}>
    <RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
        graphQLClientConfig={{ ... }} {/* 👈 Customize the Apollo Client instance */}
        <Routes />

export default App

It’s basically the same as Apollo Client’s config, with some special handling for a few props. One of those props is link, which is the Apollo Client instance’s link.

If you wanted to customize it, it was cumbersome. If you weren’t adding a link to the very front of the array, you had to know the order of the links, which basically meant reading the source. In this major, A contributor added a better API: the argument passed to link is now an array of objects with names instead of just an array of links.

Here’s an example of a console link before and after:

const link = (redwoodApolloLinks) => {
  const consoleLink = new ApolloLink((operation, forward) => {
    return forward(operation)
-  return ApolloLink.from([consoleLink, ...redwoodApolloLinks])
+  return ApolloLink.from([consoleLink,{ link }) => link)])

Firebase major versions

For Firebase users, firebase (used on the web side) and firebase-admin (used on the api side) have released major versions v10 and v11 respectively. To upgrade, we didn’t have to make any changes besides upgrading the packages’ versions, but as always, the ease of upgrading will ultimately depend on your use of firebase. Please refer to the packages’ release notes:

There’s one important thing to note: the version of firebase-admin in your project must be the same as Redwood’s version ( v11.10.1):

// api/package.json

   "name": "api",
   "version": "0.0.0",
   "private": true,
   "dependencies": {
     "@redwoodjs/api": "v6.0.0",
     "@redwoodjs/auth-firebase-api": "v6.0.0",
     "@redwoodjs/graphql-server": "v6.0.0",
-    "firebase-admin": "10.x.x"
+    "firebase-admin": "11.10.1"

Supertokens major versions

For SuperTokens users, SuperTokens has released a few major versions of supertokens-node and a few minor versions of supertokens-auth-react (which are breaking because it’s not v1). To upgrade, we recommend re-running the setup command:

yarn rw setup auth supertokens -f

One troubleshooting note: if you’re running into errors during dev after upgrading, we recommend clearing your browser’s application storage.

For more details about these majors and minors, see the supertokens-node release notes (look for v13 to v15) and the supertokens-auth-react release notes (look for v0.33 to v0.34).


Struggling with storybook.

Needed to also change our index.css as we were layering fonts:

@layer base {
  @font-face {
    font-family: 'My font';
    src: url('/public/fonts/light/e79e1efc-829a-4bc2-94d7-28d564d2ca24.woff2')
    font-weight: 300;
    ascent-override: 97%;

That was the old config, but it doesn’t work with vite, so the public has to be removed.

Last time I checked, it then broke in storybook. But I can’t check now, due to storybook always crashing :smiley:

1 Like

My api tests also seem unhappy

There seems to be something different with the user/currentuser object, I’m seeing failures all around that and even the roles

For e.g. it seems like this doesn’t work anymore

const currentUserId = context.currentUser?.id
1 Like

Hi @razzeee, thanks for highlighting an issue with the api tests. We did make some changes to how we handle context isolation which is likely causing your issues. Is it just any api test that uses context.currentUser?

It seems like it. Not sure if that also affects mockCurrentUser and hasRole.

Hi @razzeee in v6-rc a few changes were made to context so thy can explain the behavior you are seeing.

But we’re going to need some help here to replicate and investigate and make any fixes.

What would help is

  • an example of a test test that passed an now fails to se how that test uses context
  • or a small reproducible app we can Pullman down and test with

For example, is this an api test that uses the testing mocks or set context another way?

Is this a test where you are making a plugin and it uses extendContext?

Is this a where the gql handler uses the context attribute?

Any and all example and detail will help here.

The most simple one I can think of is.

 test('A user with Admin role should have access', async () => {

    const result = hasRole('Admin')

Where mockUserRole is

export const mockUserRole = (roleName: string, id = 1) => {
  const user = {
    id: id,
    name: 'Max Power',
    insertedAt: new Date('2022-06-16T15:24:19Z'),
    updatedAt: new Date('2022-06-16T15:24:
    roles: [roleName],
    role: {
      id: 1,
      name: roleName,
      insertedAt: new Date('2022-06-16T15:24:19Z'),
      updatedAt: new Date('2022-06-16T15:24:19Z'),
  return user

Thanks will have a look.

Can people use Redwood 6 but with Storybook 6 and not Storybook 7?

Thanks for trying this out @razzeee! I’m not sure I have enough information to reproduce this one. I tried setting up an example with the CSS rules you posted using the framework’s test project, and everything was working. We haven’t changed how the public assets are served in the Vite config.

I didn’t mention this in the upgrade guide, sorry for the omission, but Storybook actually still uses Webpack. I’ll update that now; we want to land Vite support for Storybook, but didn’t have enough bandwidth at the time of this RC. It’s something we’ll try to look into now. I’m not sure if that could be the reason. I’ll DM you and see if we can arrange a time so I can take a closer look.

1 Like

We don’t have plans to enable this; I see Storybook working on a fix for it in v7.1. I’ll ask about their plans for it, maybe it’ll be out soon.

Is it possible to create a new project with v6.0 from scratch?
When I run yarn create redwood-app ./myapp the project is created with version 5.4 as expected, but how to force a RC version?

I’m trying to create a new app with yarn create redwood-app and then upgrade to v6.0.0 RC following instructions for the RC.

After I do yarn rw upgrade -t rc, yard rw dev throws en error

web | node:events:491
web |       throw er; // Unhandled 'error' event
web |       ^
web |
web | Error: spawn rw-vite-dev ENOENT
web |     at ChildProcess._handle.onexit (node:internal/child_process:283:19)
web |     at onErrorNT (node:internal/child_process:476:16)
web |     at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
web | Emitted 'error' event on ChildProcess instance at:
web |     at ChildProcess._handle.onexit (node:internal/child_process:289:12)
web |     at onErrorNT (node:internal/child_process:476:16)
web |     at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
web |   errno: -2,
web |   code: 'ENOENT',
web |   syscall: 'spawn rw-vite-dev',
web |   path: 'rw-vite-dev',
web |   spawnargs: []
web | }
web |
web | Node.js v18.14.2
web | yarn cross-env NODE_ENV=development rw-vite-dev  exited with code 1

Running codemods mentioned in the forum thread doesn’t fix the issue.

@chicoxyzzy I think you must have missed the “set up Vite” step early on. Could you double check?

Hey @fmiranda, @Tobbe has worked on this recently, but I don’t think it’s possible yet.

1 Like

Thank you! I’ve missed this step somehow indeed!

1 Like

I think there is a regression on CORS with v6.0.0 RC

Testing with no auth, using

apiUrl = "http://localhost:8911"

cors: {
    origin: 'http://localhost:8910'

5.4.3 ==> API works
6.0.0 RC ==> preflight (OPTIONS query) stuck in ‘pending’ forever

I can reproduce with
yarn create redwood-app
and a sdl/cell/page on the UserExample prisma snippet

Hey @mat, thanks for trying out the RC—happy to investigate. Is that config block you posted in the graphql.ts function?

Hi @dom, thanks!

In graphql.ts function createGraphQLHandler

cors: {
    origin: 'http://localhost:8910'

In redwood.toml [web]

apiUrl = "http://localhost:8911"