Redwood 3.0.0 is now available!

Today we’re announcing Redwood v3.0.0! :rocket:

The original motivation for this major release was to drop support for Node.js v12. Many NPM packages and deployment providers that Redwood depends on and supports dropped support for Node.js v12, so it was time for us to do it too.

At the same time, many of Redwood’s fundamental dependencies, like Fastify, Jest, and Prisma, released a major version of their own, so it made a major even more fitting.

Finally, there are a few features we want to ship that have breaking changes. They were waiting for a major to come around to land, so we’re happy to ship them today! In this release you can also look forward to Cell and route parameter prerendering, TypeScript Strict Mode, WebAuthn Support, and more.

We’ve tried to make the upgrade path as smooth as possible. See the How to Upgrade section below for step-by-step instructions. And join us for a live stream on all the new features in this release (the exact date is still TBD, but in the coming weeks):

v3.0.0 Highlights :tada:

Cell and Route-parameter Prerendering

3.0 features a major new capability to Redwood’s prerendering: Cells and routes with parameters can now be prerendered! Given a set of route parameters, Redwood can now fetch data from your GraphQL API at build-time. This means you can use Redwood as a proper static site generator. You can read more in the new Dynamic routes and Route Hooks and Cell prerendering sections of the prerender docs.

TypeScript Strict Mode

While Redwood has supported TypeScript for a while, if you’ve ever turned on strict mode before, you may have been overwhelmed by red squiggles. @danny and @Tobbe have spent a ton of time this release making sure that the TypeScript strict-mode experience in Redwood is red-squiggle free. And even if you’re not using strict mode, there’s still a lot to look forward to as they’ve improved the types across the board, especially for resolvers (i.e. your service functions). Lastly, no feature is complete without docs. We revamped the one on TypeScript.

Fastify v4 and Configuring the Fastify Instance

Previously, the api side’s server.config.js file only exposed a limited number of Fastify options. But you told us that being able to configure the Fastify instance would be really useful. The server.config.js file now exposes a hook to configure plug-ins on a per-side basis.

We’ve also upgraded Fastify to v4. Check out the announcement post for all the details:

Jest v29 and Improvements in Test Memory Usage and Performance

Redwood v3 upgrades Jest from v27 to v29. v28 and v29 bring a lot of new features and fixes such as sharding. Check out the blog posts for all the details:

We also made it a point to reduce the amount of memory tests use. We want to make sure Redwood scales so we’ll continue to refine configs like these and to ensure they’re optimal now that they’re established.

Prisma v4

Redwood 3.0 bumps the version of Prisma from v3.15.2 to v4.3.1. Prisma v4 features new Prisma Client APIs, improvements to the Prisma Schema, and the general availability of several preview features. See the release notes for all the details:

WebAuthn Support

Did you know that websites can make use of TouchID/FaceID/biometric devices, not just apps? We didn’t, until a couple of months ago! The experience is so nice that we added support for it to dbAuth. Now when a user comes to your site you can log them in the first time with username/password, then ask if they want to use TouchID/FaceID going forward. If they do, when they return to the site they can just scan their fingerprint to log in.

WebAuthn adds a new cookie, which has a separate expiration date from the regular session cookie. So you can set the session cookie to expire in, say, a day, but keep the WebAuthn cookie around for two weeks. If the user comes back after 24 hours they simply scan their fingerprint again. If they haven’t been to the site in two weeks then they’ll need to use their username/password.

If you’re starting a new app, you’ll be prompted during yarn rw setup auth dbAuth and yarn rw generate dbAuth asking if you want to enable WebAuthn support. If you do, just hit y and follow the post-install instructions.

If you have an existing app, follow the exhaustive guide here for instructions on adding support to your current codebase. If you haven’t made a ton of changes to api/src/functions/auth.js or the Login/Signup pages, it may be easier to just run the setup and generate commands again with the --force option, then checking the diff of the changes to see what was added. Then just copy/paste your custom code back into the re-generated files.

CLI Performance

The Redwood CLI can be painfully slow. There’s a lot we can do to improve performance, and we plan to do it. This release, we finished some preliminary work that should pave the way for real gains in the future. While it was just preliminary, you should experience faster startup times for the CLI overall already.

Changelog

View the complete Changelog on GitHub:

How to Upgrade

We’ve tried to make this upgrade as smooth as possible. Take a look at the breaking changes below one by one. Where possible we’ve included codemods. Odds are that not all of the changes below will be breaking for you, but we’ve taken care to document edge cases that may be.

Breaking Changes

Cell and Route-parameter Prerendering

:information_source: This is more of a feature than a breaking change, but there’ s a few edge cases where it could be breaking

v3 features a major new capability to Redwood’s prerendering: Cells and routes with parameters can now be prerendered! Given a set of route parameters, Redwood can now fetch data from your GraphQL API at build-time. This means you can use Redwood as a proper static site generator!

Before, Redwood couldn’t prerender routes with parameters:

<Route path="/blog-post/{id}" page={BlogPostPage} name="blogPost" prerender />

Here, the value of id isn’t known at build-time. Before Redwood would just silently ignore this route (even though it has the prerender prop). But now you can tell Redwood what ids to prerender by creating a BlogPostPage.routeHooks.{js,ts} file next to BlogPostPage.{js,ts}:

// BlogPostPage.routesHooks.{js,ts}

export function routeParameters() {
  return [{ id: 1 }, { id: 2 }, { id: 3 }]
}

In these *.routeHooks.{js,ts} files, you have full access to your project’s api side—the database (via Prisma), services—should you need it. Just import { db } from '$api/src/lib/db'. If you’re using TypeScript, your project’s web-side tsconfig will need a small edit to know about this path. We wrote a codemod to make this change for you:

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary tsconfig-for-route-hooks

That’s the gist of it for route parameters. You can read more in the new Dynamic routes and Route Hooks section of the prerender docs. To prerender Cells, you don’t need any special config, but you should note that prerendering always happens as an unauthenticated user. The new Cell prerendering section in the docs goes into more detail.

All together, while this feature probably won’t break your project, it could for one of the following reasons:

  • if you were previously prerendering pages with route parameters, they used to just be silently ignored. Now they’ll fail and tell you that you need to create a routeHooks file. You can either implemented a routeHooks file, or add prerender={false} to the route you wish to skip.
  • if you were previously prerendering pages that had Cells that need auth. Redwood used to just render the Loading component, but now it tries to execute the query and will error out
  • your project already has a file that ends in .routeHooks.{js,ts} along side the page file that exports a routeParameters function (this is pretty unlikely)

TypeScript Strict Mode

:information_source: This change only affects TypeScript users. There’s at least one change you’ll have to make (documented below) whether or not you’re using strict mode.

While Redwood has supported TypeScript for a while, if you’ve ever turned on strict mode before, you may have been overwhelmed by red squiggles. @danny and @Tobbe have spent a ton of time this release making sure that the TypeScript strict-mode experience in Redwood is red-squiggle free. And even if you’re not using strict mode, there’s still a lot to look forward to as they’ve improved the types across the board, especially for resolvers (i.e. your service functions).

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary update-resolver-types
Open to see manual steps

Let’s say you have a Post service, and Posts have an Author. At the bottom of the Posts service file, we usually define the “relation” or field resolvers. We have better types for those resolvers now.

In essence PostResolvers becomes PostRelationResolvers:

// services/posts.ts

export const post: QueryResolvers['post'] = /**/
export const createPost: MutationResolvers['createPost'] =  /**/

// 👇 these are your "relation" or field resolvers
- export const Post: PostResolvers = {
+ export const Post: PostRelationResolvers = {
  author: (_obj, gqlArgs) => /**/
}

If you’re actually going to enable strict mode, there’s a few manual changes you’ll have to make after setting strict to true. Start here and follow along: TypeScript Strict Mode | RedwoodJS Docs.

Remember to regenerate your types by running yarn rw g types at the end of your upgrade!

Fastify v4 and Configuring the Fastify Instance

:information_source: This change affects all users and can be applied via a codemod, or by manually updating the api/server.config.js file

Previously, the api side’s server.config.js file only exposed a limited number of Fastify options. But you told us that being able to configure the Fastify instance would be really useful. The server.config.js file now exposes a hook to configure plug-ins on a per-side basis.

You’ll need to update your api side’s server.config.js either via a codemod or manually.

To do so via codemod:

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods@canary configure-fastify

If you choose to update manually, you’ll need to replace the api/server.config.js with the latest version found here. Then, update the following config to include any FastifyServerOptions that your prior configuration used (such as a custom requestTimeout value, etc):

/** @type {import('fastify').FastifyServerOptions} */
const config = {
  requestTimeout: 15_000, // 👈 update these config settings to match your prior config
  logger: {
    // Note: If running locally using `yarn rw serve` you may want to adust
    // the default non-development level to `info`
    level: process.env.NODE_ENV === 'development' ? 'debug' : 'warn',
  },
}

Want to see an example of what’s possible now? Expand the “Configuring the Fastify Instance” section below and follow along:

Configuring the Fastify Instance

For example, here you can register the compress and rate-limit Fastify plug-ins that will deflate or gzip your app’s responses and also enforce how many requests can be made to the api over a certain period of time. And, on the web-side, responses will have HTTP ETags generated.

Warning

This configuration does not apply in a serverless deploy, but will during local development

// api/server.config.js

/** @type {import('@redwoodjs/api-server/dist/fastify').FastifySideConfigFn} */
const configureFastify = async (fastify, options) => {
  if (options.side === 'api') {
    fastify.log.info({ custom: { options } }, 'Configuring api side')

    await fastify.register(import('@fastify/compress'), {
      global: true,
      threshold: 1_024,
      encodings: ['deflate', 'gzip'],
    })

    await fastify.register(import('@fastify/rate-limit'), {
      max: 100,
      timeWindow: '5 minutes',
    })
  }

  if (options.side === 'web') {
    fastify.log.info({ custom: { options } }, 'Configuring web side')

    fastify.register(import('@fastify/etag'))
  }

  return fastify
}

For complete configuration instructions, please see Register Custom Fastify Plug-ins.

Jest v29

:information_source: This change affects all users. The least you have to do is update snapshots (yarn rw test -u), but there may be more—read on

Redwood v3 upgrades Jest from v27 to v29. A breaking change introduced in v29 likely to affect all users is the change to the snapshot format. Jest changed the snapshot format making them more readable and copy-pasteable.

This should be an easy fix. Just update the snapshots! We recommend keeping your git history clean by doing this in its own commit:

# Provided your working directory is clean
yarn rw test -u
# Scan the changes; make sure everything looks ok
git add .
git commit -m "chore: update snapshot format"
Do you prefer the older format?

If you’re a fan of the older snapshot format, you’re free to undo the changes. All you have to do is add this to the jest.setup.js config file in the api and web side of your project:

 // In `api/jest.config.js` and `web/jest.config.js`

 const config = {
   rootDir: '../',
   preset: '@redwoodjs/testing/config/jest/api',  // or web
+ snapshotFormat: {
+   escapeString: true,
+   printBasicPrototype: true,
+ }
 }

 module.exports = config

The rest of the breaking changes depend on your config and dependencies. The things that are most likely to affect you are:

If you think a dependency isn’t being resolved correctly (the error will probably be “Jest encountered an unexpected token”), expand the section below and follow along:

Did Jest encounter an unexpected token?

If Jest isn’t resolving a dependency correctly, your tests will fail with an error like this:

  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    C:\repos\accessibility-insights-web\node_modules\uuid\dist\esm-browser\index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { default as v1 } from './v1.js';
                                                                                      ^^^^^^

    SyntaxError: Unexpected token 'export'

      1 | // Copyright (c) Microsoft Corporation. All rights reserved.
      2 | // Licensed under the MIT License.
    > 3 | import { v4 } from 'uuid';
        |                          ^
      4 |
      5 | export type UUIDGenerator = () => string;
      6 |

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1773:14)
      at Object.<anonymous> (src/common/uid-generator.ts:3:26)

This may seem impossible to fix, but it’s actually somewhat straightforward:

  • as a simple debugging step, try rebuilding your project’s yarn.lock file (sometimes that’s all it takes)
  • if you think it’s a problem with one of the Redwood framework’s dependencies (you could figure this out using yarn why), make an issue
  • if it’s a dependency that’s specific to your project, follow one of the strategies in this GitHub issue comment

That comment is long. To save you some time, we chose the resolver strategy, and if you do too, just copy our resolver and a check for your package:

 // See https://github.com/redwoodjs/redwood/blob/main/packages/testing/config/jest/web/resolver.js

 module.exports = (path, options) => {
   return options.defaultResolver(path, {
     ...options,
     packageFilter: (pkg) => {
+      if (pkg.name === 'uuid' || pkg.name === 'my-troublesome-dependency') {
         delete pkg['exports']
         delete pkg['module']
       }
       return pkg
     },
   })
 }

This file should live next to the api or web side’s—whichever one has failing tests—jest.config.js. You can name it whatever you want, just be sure to specify it in jest.config.js:

 // In `api/jest.config.js` and `web/jest.config.js`

 const config = {
   rootDir: '../',
   preset: '@redwoodjs/testing/config/jest/api',  // or web
+  resolver: path.resolve(__dirname, './my-custom-resolver.js'),
 }

 module.exports = config

If that still doesn’t work, definitely make an issue!

Prisma v4

:information_source: This change affects all users, but there may be nothing actionable. Read on

Redwood v3 bumps Prisma from v3.15.2 to v4.3.1. Prisma v4 was breaking for both the framework and for projects because it dropped Node.js 12 support. But there are some changes that only break the framework and some that may (depending on your use of Prisma) only break projects.

On the framework’s side, v4 was breaking because it renamed @prisma/sdk to prisma/internals to better communicate that the package won’t be versioned with SemVer. (So if you happen to be using it in your project for some reason, note that every release could be breaking.)

On a project’s side, it depends on your use of Prisma. So there may be no breaking changes for you, but to know for sure, you’ll have to read through Prisma’s upgrade guide. We think the sections that are most likely to affect you are…

As with all Prisma upgrades, remember to regenerate your Prisma client after upgrading:

yarn rw prisma generate

Targeting Node.js v14

:information_source: This change affects all users. Wherever you’re deploying, make sure your version of Node.js is at least 14

Redwood apps now target Node.js v14 instead of v12. (This is one of the main reason we’re releasing a major as the rest of the ecosystem has decided that it’s time to deprecate Node.js v12.) Since we use Babel (and on the api-side, ESBuild) to transpile modern JS, you’ve been able to write code for Node.js 12+ all along, so you shouldn’t have to worry about this change. But do check your deployment provider. Make sure the version of Node.js your deployed code is running on is at least v14.

Fixing the import-order rule

:information_source: This change affects all users and can be applied via yarn rw lint --fix

Last major, we added an ESLint rule to Redwood projects that orders imports. (Read more about it here https://community.redwoodjs.com/t/redwood-v2-0-0-is-now-available/3299#linting-import-order-3.)

It turns out we overlooked one thing in the api/src/functions/graphql.js file. Those “glob star” imports are special (Babel “expands” them as a part of the build step), and we want to highlight that by setting them apart:

Actual behavior (two groups) Desired behavior (three groups)
import { createGraphQLHandler } from '@redwoodjs/graphql-server'

import directives from 'src/directives/**/*.{js,ts}'
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'
import services from 'src/services/**/*.{js,ts}'
import { createGraphQLHandler } from '@redwoodjs/graphql-server'

import directives from 'src/directives/**/*.{js,ts}'
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
import services from 'src/services/**/*.{js,ts}'

import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'

So this major includes a fix to the import-order rule. That should be the only change, and it should be as easy as last time:

# After stashing, or at least when your working directory is clean
yarn rw lint --fix
git add .
git commit -m "style: fix graphql.js imports"

We know these kinds of changes can be annoying, and don’t take changes to linting or formatting lightly. We don’t want to bikeshed, and want to do less not more. Let us know what you think!

Want to turn this off? You can, and it’s easy. Add this to the package.json in the root of your project:

    ...
    "eslintConfig": {
      "extends": "@redwoodjs/eslint-config",
      "root": true
+     "rules": {
+       "import/order": "off"
+     }
    },
    ...
7 Likes

Congratulations!

I haven’t posted this yesterday but could Authentication with Okta docs get some love? I tried following it and there’s not much explained if I’m doing this for the first time (I am).

Could the docs be structured more like Auth0 with step by step instructions?

2 Likes

@Sebastian definitely, we’re going to focus on auth now so it’s a perfect time to queue that up.

3 Likes

Great! Thank you @dom

1 Like

Very exciting!

I have tried to update my app, but am having some issues deploying to fly.io

The web side builds fine, but the api side errors out. I’m not sure what the problem is.

Here’s the traceback from the fly remote builder

 => ERROR [api_build 2/2] RUN yarn rw build api                                                                                                                                                                                        7.8s
------
 > [api_build 2/2] RUN yarn rw build api:
#20 2.761 (node:36) ExperimentalWarning: stream/web is an experimental feature. This feature could change at any time
#20 2.761 (Use `node --trace-warnings ...` to show where the warning was created)
#20 7.650 rw build [side..]
#20 7.650
#20 7.650 Build for production
#20 7.650
#20 7.650 Positionals:
#20 7.650   side  Which side(s) to build
#20 7.650                         [array] [choices: "web", "api"] [default: ["web","api"]]
#20 7.650
#20 7.650 Options:
#20 7.650       --help                 Show help                                 [boolean]
#20 7.650       --version              Show version number                       [boolean]
#20 7.650       --cwd                  Working directory to use (where `redwood.toml` is
#20 7.650                              located.)
#20 7.650       --stats                Use Webpack Bundle Analyzer (https://github.com/we
#20 7.650                              bpack-contrib/webpack-bundle-analyzer)
#20 7.650                                                       [boolean] [default: false]
#20 7.650   -v, --verbose              Print more               [boolean] [default: false]
#20 7.650       --prerender            Prerender after building web
#20 7.650                                                        [boolean] [default: true]
#20 7.650       --prisma, --db         Generate the Prisma client[boolean] [default: true]
#20 7.650       --performance, --perf  Measure build performance[boolean] [default: false]
#20 7.650
#20 7.650 Also see the Redwood CLI Reference
#20 7.650 (https://redwoodjs.com/docs/cli-commands#build)
#20 7.651
#20 7.656 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL. Received null
#20 7.656     at Object.openSync (node:fs:577:10)
#20 7.656     at Object.readFileSync (node:fs:453:35)
#20 7.656     at DefaultHost.readFileSync (/app/node_modules/@redwoodjs/structure/dist/hosts.js:35:15)
#20 7.656     at RWRouter.get text (/app/node_modules/@redwoodjs/structure/dist/ide.js:241:22)
#20 7.656     at Object.getterCommon (/app/node_modules/lazy-get-decorator/common.js:23:32)
#20 7.656     at RWRouter.get (/app/node_modules/lazy-get-decorator/legacy.js:19:29)
#20 7.656     at RWRouter.get sf (/app/node_modules/@redwoodjs/structure/dist/ide.js:253:21)
#20 7.656     at Object.getterCommon (/app/node_modules/lazy-get-decorator/common.js:23:32)
#20 7.656     at RWRouter.get (/app/node_modules/lazy-get-decorator/legacy.js:19:29)
#20 7.656     at RWRouter.get jsxNode (/app/node_modules/@redwoodjs/structure/dist/model/RWRouter.js:86:48) {
#20 7.656   code: 'ERR_INVALID_ARG_TYPE'
#20 7.656 }

Any ideas?

I’m happy to post any other details if it helps debug.

NOTE: I also tried this on a brand new redwood app. From my shell history:

10011  yarn create redwood-app my-redwood-project
10012  cd my-redwood-project/
10013  ls
10014  fly launch

running those steps got me the exact same error as pasted above.

@sglyon Did you ever figure this out?

Sorry I never followed up with you @sglyon—let me get a test project going right now, and I’ll check in with a core team member who deploys to fly.

1 Like

Hello @dom and @sglyon, just saw this. I encountered the same thing, to fix this I needed to copy api/db/schema.prisma and web/src/Routes.tsx before building api and web.

Let me know how it goes.

2 Likes

@callingmedic911 Thanks! That worked for me.

1 Like

Not yet, but will give it a try today!

When you say copy do you mean inside there Dockerfile?

Yep !! That worked

For reference, I added these two lines to my dockerfile just before the web build stage:

COPY api/db/schema.prisma api/db/schema.prisma
COPY web/src/Routes.tsx  web/src/Routes.tsx

Another update on this: we believe that we’ve isolated the code in the build command that’s causing the error and released a patch with the fix:

Did anybody else encounter Prisma typescript issues around Decimal and number not matching for the relation resolver when grabbing related objects with an include clause?

In schema.prisma I’ve got the following:

model Parent {
  id     Int    @id @default(autoincrement())
  kids   Kid[]  @relation("kid")
}
model Kid {
  id         Int     @id @default(autoincrement())
  parentId   Int
  parent     Parent  @relation("parent", fields: [parentId], references: [id])
}
model Toy {
  id      Int     @id @default(autoincrement())
  kidId   Int
  kid     Kid     @relation("kid", fields: [kidId], references: [id])
  value   Decimal
}

In toys.sdl

export const schema = gql`
  type Toy {
    id: Int!
    value: Float
}

In services/parents/parents.ts service resolver

export const Parent: ParentRelationResolvers = {
  kids: (_obj, { root }) =>
    db.kid.findMany({
        where: { parentId: root.id},
        include: { toys: true }
    })
}

In the service resolver I get a type mismatch for value

Type 'Decimal' is not assignable to type 'number'.

And to fix that I have to update the relation resolver to manually convert the value to a number like so:

export const Parent: ParentRelationResolvers = {
  kids: (_obj, { root }) =>
    db.kid.findMany({
        where: { parentId: root.id},
        include: { toys: true }
    }).then((kid) =>
      kid.map((k) => {
        const { toys, ...restK } = k
        return {
          ...restK,
          toys: toys.map((toy) => {
            const { value, ...restT } = toy
            return {
              ...restT,
              // CONVERTING DECIMAL TO NUBMER
              value: value.toNumber(),
            }
          }),
        }
      })
    ),
}

Is there some easier way to fix what I’m doing here? Am I maybe engaging in some anti-pattern?