Using TypeScript 4.9 with Next.js 12
Tags: typescript, nextjs
Published: 2023-02-10 23:24:35

I regularly work in a codebase running on Next.js 12, and recently updated its TypeScript dependency to 4.9, looking forward to being able to take advantage of useful new features such as [the `satisfies` operator](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator).

Unfortunately, the moment I tried to use anything 4.9-specific, things went sideways rather quickly, as the dev server and build command didn't recognize the feature, due to their use of [SWC](https://swc.rs/). Since Next.js 12 hasn't had new releases for a while, the accompanying version of SWC is now significantly out-of-date.

This wouldn't be an issue if the codebase were on Next.js 13, but we haven't made the jump yet for a variety of reasons. Fortunately, I was able to overcome this obstacle while remaining on Next.js 12; if you find yourself in a similar situation, hopefully this process can work for you as well.

## Version-Pinning SWC

Both npm and yarn provide a way to override installed versions of packages in `package.json`: npm through the [`overrides` property](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides) (only in npm 8 or higher), and yarn through the [`resolutions` property](https://classic.yarnpkg.com/en/docs/selective-version-resolutions).

Unfortunately, as I found out, yarn's `resolutions` property has [a long history of not playing well with `optionalDependencies`](https://github.com/yarnpkg/yarn/issues/6834): anything placed into `resolutions` is treated as required and will abruptly fail to install if it is, for example, a platform-specific package appropriate for your deploy environment but not your dev environment or vice versa, as is the case here.

Since this particular codebase also relied on yarn, this warranted an impromptu switch to npm to see how `overrides` would fare. Fortunately, [`synp`](https://github.com/imsnif/synp) exists to migrate between package lockfile formats. (npm also evidently supports `yarn.lock` files directly, which I only found out after converting, so I didn't put that to the test.)

npm's `overrides` had no issue working, with the following configuration:

```json
"overrides": {
  "next": {
    "@next/swc-darwin-x64": "13.1.6",
    "@next/swc-linux-x64-gnu": "13.1.6",
    "@swc/helpers": "0.4.14"
  }
}
```

By forcing the version of relevant platform-specific `@next/swc-*` packages (in this particular case, OS X for dev and Linux for CI/deployment) to their latest v13 counterparts, and updating `@swc/helpers` to match the dependency listed in v13, we can get support for TypeScript's new features while still running Next.js 12, and there don't seem to be any adverse side effects.

## Other Potential Gotchas

We've tackled the dev server and build process itself above, but other parts of your toolchain may also require updating to support TypeScript 4.9 if they haven't been in a while. Make sure to test all aspects of local development (including your code editor and its extensions), and of course keep an eye on CI processes when merging the update.

I encountered a couple more hiccups:

### Jest

The codebase relied on Jest 27, using `babel-jest` and `babel-loader`. This similarly ran into issues failing to parse TypeScript 4.9 features. I ended up doing a few things on the way to resolving this:

- Upgraded to Jest 29
- Updated a couple of packages (`isbot` and `uuid`) to incorporate fixes that resolved errors that would come up in newer versions of Jest
- Switched from `babel-jest` and `babel-loader` to `ts-jest` (note that this requires a custom `tsconfig.json` with `"jsx": "react"`, as opposed to Next requiring `"jsx": "preserve"`)

### Prettier

In my case, Prettier was still on a rather old version, so I needed to update it to 2.8 in order for `next lint` to pass, since the codebase uses `eslint-plugin-prettier`.