Policy
Follow the combination of Turborepo and pnpm workspaces. Unify the package manager to pnpm, and commit only pnpm-lock.yaml as the lockfile.
Turborepo updates quickly, and the key names in turbo.json are also changing. If you are unsure about the shape of the configuration, do not rely on memory—look it up below.
- Documentation https://turborepo.dev/docs
- turbo.json reference https://turborepo.dev/docs/reference/configuration
- Task composition https://turborepo.dev/docs/crafting-your-repository/configuring-tasks
- Caching https://turborepo.dev/docs/crafting-your-repository/caching
- Handling multiple languages https://turborepo.dev/docs/guides/multi-language
- Update information https://turborepo.dev/blog
- Releases and breaking changes https://github.com/vercel/turborepo/releases
The task definition key in turbo.json is tasks since v2. pipeline is the old name. Do not copy old articles.
Do not install Turborepo globally.pnpm exec turbo Use , or add it as a root devDependency.
Structure
my-monorepo/
pnpm-workspace.yaml
turbo.json
package.json
apps/ デプロイ可能なアプリケーション
packages/ 共有ライブラリと設定
- The workspace structure is defined in pnpm-workspace.yaml.
- Maintain the separation of apps and packages. Do not put deployable apps in packages. Do not put shared libraries in apps.
- Do not create nested packages.
Packages
- Names are
@<org>/scoped with a prefix. - For all internal packages, set
"private": true. - Define exports in each package.json. Prefer exports over main.
- Place the shared TypeScript configuration in packages/tsconfig/, and have each package inherit it.
- Reference internal dependencies with the
workspace:*protocol.
Responsibility packages
Always place the following under packages.
- log Log aggregation
- error Error aggregation
- prompt AI prompt aggregation
- env Environment variable aggregation
- db Database aggregation
Consumers add @<org>/log etc. to workspace:* as a dependency. Do not reimplement the corresponding responsibility on the caller side.
Generating and updating package.json
Do not hand-write package.json from scratch. The same applies to the root, apps/、packages/ and either.
pnpm init # package.json を生成する
pnpm create <template> # フレームワークの scaffolder を使う
Adding, updating, and removing dependencies are done with commands.
pnpm add -D -w turbo typescript # ルートの devDependency
pnpm add --filter @<org>/dashboard zod # 特定パッケージの外部依存
pnpm add --filter @<org>/dashboard --workspace @<org>/log # 内部依存を workspace:* で解決
pnpm update --filter @<org>/dashboard
pnpm remove --filter @<org>/dashboard zod
- When targeting something other than the root, specify the package with
--filter. - For workspace internal dependencies, also use
--workspace. - Do not directly edit dependencies, devDependencies, or peerDependencies.
- Only for fields that cannot be set with commands (scoping of name, private, exports, scripts), make minimal edits to the generated package.json.
Example procedure
# ルートワークスペース
mkdir my-monorepo && cd my-monorepo
pnpm init
# pnpm-workspace.yaml に apps/* と packages/* を定義する
pnpm add -D -w turbo typescript
# 責務パッケージ
mkdir -p packages/log
(cd packages/log && pnpm init)
# name を @<org>/log にし、private と exports を設定する
# アプリ
pnpm create next-app@latest apps/dashboard
pnpm add --filter @<org>/dashboard --workspace @<org>/log
turbo.json
Define a task pipeline that takes dependencies into account.
{
"$schema": "https://turborepo.dev/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {},
"test": { "dependsOn": ["^build"] },
"dev": { "cache": false, "persistent": true }
}
}
^buildspecifies that the tasks of dependency packages run first.- Write outputs accurately. If you omit them, they will not be restored from the cache.
- For resident tasks like dev,
cache: falseとpersistent: trueto be. - if environment variables affect the build result, list them in env. If you do not list them, the cache will hit even when the environment differs.
Compilation policy
The default is the JIT (Just-in-Time) compilation pattern. Switching to the compiled pattern that outputs to dist with tsc is done only when explicitly requested.
Non-JavaScript projects
Turborepo is not JavaScript-only. Projects such as Cargo, Go, and Python can also be placed on the same task graph.
For Rust (Cargo) and Python, place a package.json in each unit you want to cache independently, and call each language's toolchain from its scripts. Turborepo does not analyze the native dependency graph; it uses the relationships on the package manager.
{
"name": "@<org>/engine",
"private": true,
"scripts": {
"build": "cargo build --release",
"test": "cargo test",
"lint": "cargo clippy -- -D warnings"
}
}
Write inputs and outputs on the turbo.json side to make caching work.
{
"tasks": {
"build": {
"inputs": ["src/**", "Cargo.toml", "Cargo.lock"],
"outputs": ["target/release/**"]
}
}
}
- If you do not narrow down inputs, changes to unrelated files will invalidate the cache.
- Specifying the entire target in outputs makes the artifacts huge. Specify only the necessary paths.
- It doubles up with Cargo's own incremental cache. Decide which one to use before configuring.
Go has experimental native support. Enable futureFlags in turbo.json and list the modules in go.work. In this case, package.json is not needed on the Go package side, and the module path becomes the package identifier.
{
"futureFlags": { "experimentalGoWorkspaces": true }
}
Since it is experimental, the behavior may change. Before adopting it, check the current status in the documentation above.
Confirm before finalizing
- Are lockfiles other than pnpm (package-lock.json, yarn.lock) mixed in?
- Are deployable apps not in packages, and shared libraries not in apps?
- Internal dependencies are
workspace:*is it set to? - Does each package have private and exports, and inherit the shared tsconfig?
- Are responsibility packages not being reimplemented on the caller side?
- Is the key in turbo.json tasks? Has it not been left as pipeline?
- Are outputs, inputs, and env accurate? Is the cache not in a state where it hits incorrectly or is always invalidated?
- Are dependency additions not being done by hand-editing package.json?
- Is turbo not installed globally?