Caching and build pipelines for multiple apps in one repository
Managing multiple Vue applications and shared packages in one repository quickly becomes slow and confusing without task orchestration. Turborepo brings incremental caching, parallel task execution and declarative pipeline definitions that reduce build times in Vue monorepos from minutes to seconds.
Table of contents
- 1. Why Vue projects move into a monorepo at all
- 2. Workspace structure with pnpm and Turborepo
- 3. Defining task pipelines declaratively
- 4. Understanding and using incremental caching
- 5. Remote caching for the whole team
- 6. Shared Vue components and configuration
- 7. CI pipelines with affected packages
- 8. Common pitfalls with Vue monorepos
- 9. Turborepo tooling compared
- 10. Summary
- 11. FAQ
1. Why Vue projects move into a monorepo at all
A monorepo for Vue projects bundles multiple applications and shared packages, such as a design system or common TypeScript types, into a single repository instead of spreading them across several separate Git repositories. The immediate advantage: a change to a shared Vue component becomes visible in the same pull request that also adjusts the consuming applications, instead of having to be synchronized across multiple repositories with separate version bumps.
Without suitable tooling, however, a Vue monorepo quickly becomes a performance problem: a simple npm run build in the root directory rebuilds all packages by default, even if only a single file in a single Vue application changed. With ten or more packages, this adds up to build times that undo every development benefit of the monorepo. This is exactly where Turborepo comes in: it orchestrates tasks across package boundaries and only builds what has actually changed.
Moving to a monorepo with Turborepo pays off especially for Vue teams when multiple applications use the same internal packages, for example a shop frontend and an admin tool that both build on the same design system and the same API client types. For a single Vue project without shared packages, the additional tooling effort rarely pays off.
2. Workspace structure with pnpm and Turborepo
The common structure for a Vue monorepo with Turborepo separates applications and shared packages into two top level folders, usually apps/ and packages/. Every Vue application in apps/ has its own package.json with its own dependencies, while reusable building blocks such as the design system, an API client or shared ESLint configuration live in packages/ and are referenced by applications as internal workspace dependencies.
pnpm is the most common package manager for Turborepo monorepos, because its workspace protocol (workspace:*) links internal packages via symlink instead of resolving them through the registry. This means: a change to a shared Vue component is immediately visible in the consuming application, without a publish step between packages.
// pnpm-workspace.yaml — Defines which folders are part of the monorepo
// packages:
// - "apps/*"
// - "packages/*"
// apps/shop/package.json — Consuming an internal package via workspace protocol
{
"name": "@mironsoft/shop",
"dependencies": {
"@mironsoft/design-system": "workspace:*",
"@mironsoft/api-client": "workspace:*",
"vue": "^3.4.0"
}
}
// packages/design-system/package.json — Internal package, never published externally
{
"name": "@mironsoft/design-system",
"main": "./dist/index.js",
"peerDependencies": { "vue": "^3.4.0" }
}
3. Defining task pipelines declaratively
The centerpiece of Turborepo is the turbo.json file, which defines how individual tasks, such as build, test or lint, depend on each other. The syntax "dependsOn": ["^build"] means the build task of a package only starts after the build task of all packages it depends on has finished. For a Vue monorepo this concretely means: the shop application automatically waits for the design system package to finish building before its own build starts.
Turborepo derives this dependency graph automatically from the package.json dependencies, without it needing to be maintained manually. This is a significant advantage over hand rolled script solutions, where the build order is often hardcoded and must be manually updated for every new package.
// turbo.json — Declarative pipeline definition for a Vue monorepo
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".output/**"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
},
"lint": {
"dependsOn": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
4. Understanding and using incremental caching
The real performance gain of Turborepo in Vue monorepos comes from incremental caching. Turborepo computes a hash from all inputs of a task, including the package's source code, its dependencies and the relevant environment variables. If none of these inputs change between two runs, Turborepo reads the result, such as the dist folder of a Vue application, directly from the cache instead of running the build again.
For Vue projects, it is especially important to specify exactly, in the outputs field of every task definition, which directories count as build output, usually dist/** for Vite builds. Without this specification, Turborepo will cache the execution but cannot restore the result, which leads to a missing dist folder on a cache hit. A second important point: environment variables that influence the build output, such as the API base URL, must be explicitly listed in the env configuration, otherwise a changed variable does not invalidate the cache and an outdated build gets shipped.
5. Remote caching for the whole team
Local caching only speeds up repeated builds on the same machine. Remote caching in Turborepo shares cache results with the entire team and the CI pipeline via Vercel Remote Cache or a self hosted cache server. If a developer builds a Vue application in the morning that a colleague already built unchanged the day before, Turborepo pulls the result from the remote cache instead of rebuilding locally, even if the local cache is empty.
The biggest leverage happens in the CI pipeline: a CI runner typically starts with an empty local cache but can, via remote caching, access build results already produced by a previous CI run or a local developer build. For a Vue monorepo with multiple applications, this can reduce CI runtime from ten minutes to under a minute when only a small subset of packages actually changed.
#!/usr/bin/env bash
# .github/workflows/ci.yml (excerpt) — Enabling Turborepo remote caching in CI
set -euo pipefail
export TURBO_TOKEN="${TURBO_TOKEN}"
export TURBO_TEAM="${TURBO_TEAM}"
# Turborepo automatically checks the remote cache before running any task
pnpm turbo run build test lint --cache-dir=.turbo
# Only re-runs tasks whose input hash changed since the last cached execution
echo "[OK] CI run complete — cache hits skip redundant Vue app rebuilds"
6. Shared Vue components and configuration
A common pattern in Vue monorepos with Turborepo is a dedicated packages/ui package for shared Vue components, a packages/config package for shared ESLint and TypeScript configuration, and a packages/tsconfig package extended as a base configuration by all applications. This structure prevents every Vue application from maintaining its own, slightly different lint and TypeScript configuration.
It is important that shared packages themselves also participate in Turborepo caching. A change to the base TypeScript configuration automatically invalidates the cache of all applications depending on it, because Turborepo correctly derives the dependency graph from the workspace references. Without this automatic invalidation, a configuration change would otherwise silently ship an outdated, cached build.
// packages/tsconfig/vue-app.json — Base config extended by every Vue app
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"jsx": "preserve",
"types": ["vite/client"]
}
}
// apps/shop/tsconfig.json — Each app extends the shared base, never duplicates it
{
"extends": "@mironsoft/tsconfig/vue-app.json",
"compilerOptions": { "outDir": "dist" },
"include": ["src"]
}
7. CI pipelines with affected packages
Besides task level caching, Turborepo offers --filter to restrict CI runs to actually affected packages. A git diff against the last deployed commit shows which files changed, and turbo run build --filter=...[HEAD^1] only builds the Vue applications and packages actually affected by these changes, plus everything depending on them.
For a Vue monorepo with five applications, this means: a pull request that only touches checkout does not trigger a build of the other four applications, even without caching. Combined with remote caching for the actually affected packages, this creates a CI pipeline whose runtime is proportional to the scope of the change, not the total size of the monorepo.
8. Common pitfalls with Vue monorepos
The most common mistake in Vue monorepos with Turborepo is an incomplete outputs field, which causes Turborepo to report a cache hit while the actual build artifacts are missing. A second widespread mistake is not declaring environment dependent values such as API endpoints in the env configuration, causing a staging build to be incorrectly recognized as identical to a production build and served from cache.
A third, more subtle pitfall concerns circular dependencies between internal Vue packages: if the design system package accidentally imports from the shop application while the shop application simultaneously depends on the design system, Turborepo can no longer compute a valid dependency graph. A linting tool such as dependency-cruiser, detecting circular dependencies in the CI process, prevents this problem before it leads to cryptic Turborepo error messages.
#!/usr/bin/env bash
# ci/check-circular-deps.sh — Catch circular package dependencies before Turborepo does
set -euo pipefail
npx depcruise packages apps \
--config .dependency-cruiser.js \
--output-type err-html \
--output-to dep-report.html
# dependency-cruiser exits non-zero when a circular dependency rule is violated
echo "[OK] No circular dependencies found between Vue monorepo packages"
9. Turborepo tooling compared
For monorepo tooling in Vue projects, besides Turborepo there are other established tools available, each making different tradeoffs between configuration effort, feature scope and ecosystem maturity.
| Tool | Configuration effort | Remote caching | Distinguishing feature |
|---|---|---|---|
| Turborepo | Low | Built in | Simple turbo.json, fast onboarding |
| Nx | High | Built in (Nx Cloud) | Most extensive features, code generators |
| pnpm workspaces (alone) | Minimal | None | Only dependency linking, no task caching |
| Lerna (classic) | Medium | Via Nx integration | Historically common, usually combined with Nx today |
For most Vue teams starting fresh with a monorepo, Turborepo is the most pragmatic choice due to its low configuration effort and fast setup time. Nx pays off once code generators, dependency graph visualization or deeper IDE integration are additionally needed, but comes with a steeper learning curve.
Mironsoft
Monorepo architecture, Turborepo and fast Vue build pipelines
Drastically cut CI runtimes for your Vue monorepo?
We migrate your Vue projects into a Turborepo structure with correct task pipelines, remote caching and affected based CI runs that only build what actually changed.
Workspace setup
Building pnpm workspaces with a clean apps/packages structure
Caching strategy
Configuring turbo.json pipelines and remote cache for team and CI
CI optimization
Affected based pipelines that only build impacted Vue apps
10. Summary
Monorepo Vue projects with Turborepo solve the scaling problem that arises once multiple Vue applications and shared packages are managed in one repository. A clear apps/ and packages/ structure with pnpm workspaces forms the foundation, while turbo.json declaratively defines how tasks depend on each other. Incremental caching with a correctly configured outputs field drastically reduces build times once a package's inputs have not changed.
Remote caching shares this time savings across the entire team and the CI pipeline, while affected based filters additionally ensure that only actually impacted Vue applications get built at all. Anyone who avoids the typical pitfalls around outputs configuration, environment variables and circular package dependencies gets, with Turborepo, one of the most pragmatic solutions for fast, maintainable Vue monorepos.
Monorepo Vue Projects with Turborepo — the essentials at a glance
Workspace structure
apps/ for applications, packages/ for shared packages, linked via pnpm workspaces.
Task pipelines
turbo.json with dependsOn defines build order automatically from the dependency graph.
Caching
Correct outputs field and env list prevent outdated, falsely cached builds.
CI optimization
Remote caching plus --filter for affected packages drastically reduce CI runtimes.