TypeScript 7 migration notes from a monorepo
The number everyone quotes is 10x. Microsoft says 8x to 12x on full builds, and the VS Code repo went from close to a minute of project load to about ten seconds. I did not believe any of it until I ran it on my own code, because compiler speedups tend to be measured on the repos that the compiler team optimises for.
Here is the honest result on a six package pnpm monorepo with Turborepo, about 41,000 lines of TypeScript, a Next.js app, a UI package and four libraries. tsc --noEmit across all eight typecheck tasks took 38 seconds cold on TypeScript 5.9. On 7.0.2 it takes 4.1 seconds. In the editor, which is where it matters more, the first hover after opening a file used to lag for a second or two on the big files. Now it does not lag at all.
So the number is real. The migration is also not free, and the parts that cost time are not the parts I expected.
What the port is, and what it is not
TypeScript 7 is a port of the compiler and language service from TypeScript to Go. Not a rewrite. The team was careful about that word, and it matters: the type checking semantics are meant to be identical to 6.x, which is the last TypeScript-in-TypeScript release and the bridge version. If 7 says your code has an error that 6 did not report, that is a bug, not a new rule.
What you get is a native binary, real parallelism across files, and no garbage collected JavaScript heap sitting between you and the checker. That is where the speed comes from. There is no new inference algorithm, no new type system feature, nothing to learn about the language itself.
What you do not get in 7.0 is a stable programmatic API. If you have anything that calls ts.createProgram, walks the AST with ts.forEachChild, or builds on the language service directly, it will not work against the Go compiler yet. Microsoft says 7.1 brings a new API. Until then, those tools stay on the 6.x line.
That one paragraph is the whole migration risk. Everything else is a version bump.
Step one: find out who talks to the compiler API
Before touching a package.json, list every tool in your build that uses TypeScript as a library rather than as a command. In my monorepo that came to:
pnpm why typescriptThe output was longer than I wanted. Next.js, Biome, tsup, a custom MDX plugin, a codegen script for the site config schema, and typescript-eslint pulled in transitively by something I had forgotten about. Not all of these matter. The question for each is: does it run the compiler, or does it just need the typescript package to exist?
Biome does not touch the TypeScript API at all, it has its own parser. Next.js 16 with Turbopack runs its own type stripping and only calls tsc for the typecheck step during next build, which is a command line invocation and works fine. tsup uses esbuild for transpilation and tsc for declaration files, also a command. The codegen script was the problem. It imported typescript and walked a schema file to emit a JSON document. That had to stay on 6.x.
The clean way to run both is to keep typescript at 7 in the workspace root and pin the one package that needs the API to 6:
{
"name": "@zeybek/codegen",
"devDependencies": {
"typescript": "6.0.4"
}
}pnpm isolates that install, so the script sees 6 and everything else sees 7. If you use npm or Yarn with hoisting, you will need an alias or an override, and it is fiddlier.
Step two: the config cleanup you were putting off
TypeScript 6 deprecated a batch of tsconfig options and 7 removes them. The list that hit me:
baseUrl is gone. If you had it purely so that paths would work, delete it, because paths resolves relative to the config file now. If you had it so that bare imports resolved from src, you need to switch those imports to a path alias. I had four files that imported shared/components without a leading @/ and they had worked for years by accident.
moduleResolution: node (the old one, now called node10) is out. Almost everything should be on bundler or nodenext by 2026 anyway. My UI package was still on node because nobody had a reason to change it. Moving to bundler surfaced two imports of lodash/debounce style deep paths that needed the .js extension under the new rules.
target: ES5 and ES3 are gone. I did not have those, but I have seen legacy configs that still do.
esModuleInterop and allowSyntheticDefaultImports are now always on. Delete them from the config, they are noise.
The deprecation messages in 6 tell you exactly which line to fix, so the right order is: upgrade to 6.0 first, run tsc, fix every deprecation warning, then upgrade to 7. Going straight from 5.9 to 7 gives you errors without the helpful explanation.
Step three: the actual upgrade
pnpm -r update typescript@7 @types/node@latest
pnpm typechecknpm install -D typescript@7 --workspaces
npm run typecheck --workspacesThe typescript package on npm now ships the Go binary for your platform as an optional dependency, the same way esbuild and Biome do. The tsc command is the same name and takes the same flags. The @typescript/native-preview package that existed during the preview period is not needed any more, and if you had it installed, remove it, because having both on the path is confusing.
There is one behavioural change I hit immediately. TypeScript 7 checks projects in parallel, and it reports errors in a different order than 6 did. If you had a test that snapshotted tsc output, or a CI step that grepped for the first error, the ordering is no longer deterministic across runs. I had a Turborepo task that compared error counts between branches. The counts still match. The order does not, and that is fine.
The editor is where you feel it
The command line speedup is nice for CI. The reason to do this today is the editor.
VS Code 1.104 and later ship the native language service behind typescript.experimental.useTsgo, and with TypeScript 7 in the workspace it becomes the default. What that changes day to day:
Hover, go to definition, and find all references on a large file no longer have the half second pause. Rename symbol across the monorepo went from "start it and go make tea" to under a second for the 600 reference rename I tried. The "Loading TypeScript project" spinner on startup, which used to sit there for 20 to 30 seconds on this repo, is gone.
Memory is the other thing. The old language service on this monorepo sat at 1.4 GB after an hour of editing. The Go one sits at 380 MB and stays there. On a laptop with 16 GB and a browser open, that is the difference between the fan spinning up and not.
If you use another editor, the language server protocol side is the same binary, so Neovim with typescript-language-server picks it up the moment the plugin points at the new tsserver path. Zed and JetBrains shipped support during the RC period.
What broke, honestly
Two things, both small, both mine.
The first was a .d.ts file with a triple slash reference to a types package that did not exist any more. TypeScript 5 silently ignored it. 7 reports it as an error, which is correct, and I deleted the line.
The second was a type test using @ts-expect-error above a line that 6 flagged as an error and 7 does not. That looks like a semantic difference, so I dug in. The line was a generic call with a conditional type that 5.x resolved to never because of a known inference limitation. 6 fixed the inference. 7 inherits the fix. The @ts-expect-error was papering over a compiler bug that no longer exists, so the "unused expect error" report was the compiler telling me the truth. That is the only type checking difference I found in 41,000 lines.
CI, briefly
The GitHub Actions job that ran tsc across the monorepo took about 50 seconds including setup before the migration. It takes about 15 now, and 11 of those are the runner starting and pnpm restoring its store from cache. The typecheck itself is the 4 seconds from the top of this post. For a job that runs on every push to every branch, that is the difference that let me move the typecheck from a merge-only check to a per-push check with no complaints about waiting, which catches type errors two hours earlier on average than it did before.
When not to upgrade yet
If your build is built on ts-morph, a custom transformer plugin through ttypescript or ts-patch, a generator that walks the AST, or an older version of typescript-eslint that runs type aware rules through the TypeScript API, wait for 7.1 or pin those tools to 6 the way I did with the codegen script. The type aware lint rules are the common case. typescript-eslint has a compatibility layer in progress but as of this week it runs against 6.
If you are on Angular, follow Angular's own guidance, because the CLI has a tighter coupling to the compiler than most frameworks.
One more case: a very large single project, the kind with a 5,000 file include and no project references, will see the smallest relative gain. The Go compiler parallelises across files, and it is still fast, but the old advice about splitting a monolith into referenced projects still stands. On the monorepo here, where each package is already its own program, the parallelism had eight things to chew on from the start, and I suspect that is part of why my numbers came out at the top of Microsoft's range rather than the bottom. If you are at the bottom of the range, project references are the next lever, and they were worth doing before 7 too.
Everyone else: do the 6 step first, fix the deprecations, then flip to 7. The upgrade itself is a version number. The speed is not a marketing figure, it is the largest single improvement to my day to day tooling since Turbopack, and I have no reason to go back.
Check pnpm why typescript (or the npm equivalent) before anything else. The list of packages that import the compiler as a library is your entire risk. If it is empty, the upgrade takes ten minutes.