When you start thinking about TypeScript monorepos, your options quickly expand. pnpm, bun, Turborepo, Nx, Changesets, Biome. All of them seem correct, but if you put them all in from the beginning, it will be heavy. On the other hand, if you cut everything, you will be in trouble later.
The reason this kind of discussion is difficult to organize is that we only look for the “optimal solution” without considering the premises. It is better to separate the general-purpose, easy-to-understand solution and the practical solution for the configuration in front of you.
This time, I will organize the following two parts.
- Safe configuration when creating a new TypeScript monorepo
- Realistic configuration based on the specific assumptions of
apps/web,apps/wxt,packages/ui,packages/core
First, the conclusion
| Prerequisites | First configuration to choose | Supplement |
|---|---|---|
| New generic monorepo. In the future, package release, CI optimization, and Docker integration will be considered | pnpm workspace + Turborepo + TypeScript Project References + package.json of exports + Changesets | The most stable |
4 package configuration: apps/web, apps/wxt, packages/ui, packages/core. I don’t use Docker. Do not publish shared packages to npm | pnpm workspace + Biome + TypeScript Project References | Turborepo is not needed at this stage |
| Bun provides the execution environment, package manager, and testing | bun workspace is also a candidate | However, pnpm is strong in terms of safe operation |
In short, pnpm + turbo is strong in general theory. However, in this case, workspace only is sufficient.
General purpose non-difficult
As of 2026, if you want to start without worrying deeply, pnpm workspace is the most stable foundation. workspace is provided as standard, local package references can be made explicit with workspace: protocol, and target packages can be narrowed down considerably with --filter.
Use TypeScript Project References for type boundaries. The TypeScript official documentation also clearly states that split projects can improve build time, logical separation, and build in dependency order using tsc --build. This is quite effective in monorepo.
If you add Turborepo on top of that, you will have task graph, cache, parallel execution, and turbo prune all at once. [Turborepo’s task settings] (https://turborepo.dev/docs/crafting-your-repository/configuring-tasks) only needs to be assembled around dependsOn and outputs, and there is room for growth as the repo grows.
If you have a package to publish, Changesets will naturally fit in as well. pnpm’s workspace documentation also assumes that a dedicated tool such as Changesets is used for versioning of packages in a workspace.
In other words, the general-purpose non-difficult solution is as follows.
| Role | Recommendation |
|---|---|
| workspace management | pnpm workspace |
| Type Boundaries and Incremental Builds | TypeScript Project References |
| Task execution, parallelization, and caching | Turborepo |
| public face of package boundary | exports of package.json |
| versioning / changelog | Changesets |
| lint / format | Biome or ESLint + Prettier. It’s easier to start with Biome now |
Why is Turborepo still not needed in this premise?
The configuration this time is fairly simple.
packages/corepackages/ui -> packages/coreapps/web -> packages/ui, packages/coreapps/wxt -> packages/ui, packages/coreIf the dependencies are at this level, running the filter on pnpm workspace and tsc -b is sufficient for operation.
For example, the value of including Turborepo increases all of a sudden in situations like this.
| Pain | Why Turborepo works |
|---|---|
| Total build / test / lint time is long | Caching and parallel execution are effective |
| I want to clarify the dependency order of tasks | dependsOn can express a graph |
| I want to speed up CI | Remote cache works |
| I want to extract only the necessary packages for Docker | turbo prune can be used |
On the other hand, if you install it at a stage where you are not yet in such trouble, it is difficult to see the value beyond adding one more configuration file. If you turn off Docker, the appeal of prune will drop a notch. Changesets is also not required if you do not publish shared packages on npm.
Therefore, based on this premise, it is reasonable to start with pnpm workspace + Biome + TypeScript Project References.
How to cut layers
In this four-part division, it is more important to avoid ambiguity in roles than in tool selection.
| Path | Things to put | Things you shouldn’t put |
|---|---|---|
packages/core | domain, schema, validation, API client, pure function, state model | UI, routing, browser API |
packages/ui | Environment-independent React component / hook | WXT-specific processing, router, extension storage |
apps/web | Assembly as a web application, page, routing, adapter | extension specific circumstances |
apps/wxt | background / content script, browser API, WXT-specific settings | Web-specific routing, pure logic that can be placed in the shared layer |
The last thing I want to avoid is turning packages/ui into a place for all things that are shared. When browser extension specific circumstances and routing start to enter here, it becomes a connection point rather than a shared layer. If that happens, the benefits of monorepo will diminish.
Notes on apps/wxt
You should be a little careful about apps/wxt. WXT TypeScript settings usually extends .wxt/tsconfig.json from root’s tsconfig.json. However, monorepo may not take that form, in which case you need to include .wxt/wxt.d.ts in your TypeScript project.
In other words, although apps/wxt is part of the monorepo, it is more stable to treat it as an independent TypeScript project.
Do not use tsconfig.paths as the primary means of cross-border references
This is quite important.
TypeScript 5.7 release notes clearly states that imports that depend on baseUrl and paths cannot be rewritten when outputting JS. In other words, a design that crosses package boundaries using only tsconfig.paths may pass type checking, but is likely to be distorted at the execution or publishing stage.
Basically, you should consider cross-border references in monorepo in the following order.
- Separate packages
- Connect dependencies with
workspace: - Decide the public side with
exportsofpackage.json - Create a dependency order build with
TypeScript Project References
paths is less likely to be damaged if it is kept as an auxiliary within the package.
Biome fits well with this premise
I think the decision to remove ESLint and focus on Biome is quite natural. Biome’s big projects guide also organizes the premise of using root config and nested config differently for large repos such as monorepo and workspace. In v2, a configuration based on monorepo is also introduced.
At this scale, it would be more straightforward to send the lint/format to Biome and take care of only the project boundary and build on the TypeScript side.
Minimal configuration image
This is enough for the initial setup.
- Place
pnpm-workspace.yamlin root - Place solution-style
tsconfig.jsonin root andreferenceseach package packages/coreandpackages/uienablecomposite- Shared package specifies
exportsofpackage.json - Place
biome.jsonin root - For
apps/wxt, check separately how to handle WXT type definitions.
At this point, you can fight for quite a long time.
When to add the next tool
| What to add | When to add |
|---|---|
Turborepo | Build / test / lint is heavy, CI optimization is required, I want to clarify the task graph |
Changesets | I want to version-manage and publish a shared package |
Nx | I wanted automatic generation, polyglot, and stronger monorepo management |
Migrating to bun workspace | I wanted to unify runtime / package manager / test to Bun |
With monorepo, it works better to add only where the pain occurs, rather than adding everything from the beginning.
summary
There is no single optimal solution for TypeScript monorepo. pnpm workspace + Turborepo + TypeScript Project References is strong for general-purpose non-difficult answers. However, if Docker and package publishing are not too heavy yet after dividing into apps/web, apps/wxt, packages/ui, and packages/core, it would be lighter and less likely to break down if you start with pnpm workspace + Biome + TypeScript Project References.
The important thing is not to increase the number of tools, but to keep core and ui thin, and express cross-border references honestly with workspace: and exports. If that doesn’t collapse, it’s not too late to add turbo later.
hsb.horse