Optimizing CI/CD for Full-Stack Projects: Leveraging Turborepo's Remote Caching and On-Demand Builds
Lukas Schneider
DevOps Engineer · Leapcell

Introduction
Modern full-stack JavaScript applications are increasingly complex, often adopting a monorepo architecture to manage multiple interdependent services, frontend applications, and shared libraries. While monorepos offer significant advantages in terms of code sharing and simplified dependency management, they can also introduce challenges, particularly in Continuous Integration/Continuous Delivery (CI/CD) pipelines. A common bottleneck is the time and resources wasted rebuilding unchanged parts of the codebase on every commit. Imagine a scenario where a small CSS change in one frontend app triggers a full rebuild and retest of every single service and application in your monorepo – this is not only inefficient but also costly in terms of cloud resources and developer waiting time. This article aims to address these challenges by exploring how Turborepo's powerful combination of remote caching and on-demand builds can dramatically optimize CI/CD processes for full-stack JavaScript projects, ensuring faster feedback loops and reduced infrastructure costs.
Core Concepts and Implementation
Before diving into the specifics of optimization, let's establish a clear understanding of the core concepts central to Turborepo's effectiveness.
What is a Monorepo?
A monorepo is a single repository containing multiple distinct projects, often with shared dependencies and configurations. In a typical full-stack JavaScript setup, this might include a React frontend, a Node.js backend API, common UI components, and utility libraries, all residing within the same Git repository.
What is Turborepo?
Turborepo is a high-performance build system for JavaScript and TypeScript monorepos. It is designed to optimize the development and CI/CD experience by intelligently caching build artifacts and only rebuilding what's necessary.
Turborepo's Key Features
Two features are particularly relevant to our discussion:
-
Remote Caching: Turborepo can store build artifacts (e.g., compiled code, test results, bundled assets) in a remote cache. This cache can be shared across multiple developers and CI/CD pipelines. If a particular task (like
build
ortest
) for a specific project has been executed before with the same inputs, Turborepo can retrieve the cached output instead of re-running the task. -
On-Demand Builds (or Incremental Builds): Turborepo analyzes the dependency graph of your monorepo and determines which projects and tasks have been affected by changes. It then only runs the tasks for those affected projects, skipping unaffected ones. This significantly reduces build times by avoiding redundant work.
Setting Up Turborepo
First, you'll need to set up Turborepo in your monorepo. Assuming you have a package.json
at the root of your monorepo and individual package.json
files within your workspace packages (e.g., apps/web
, packages/ui
):
// root package.json { "name": "my-fullstack-monorepo", "version": "1.0.0", "private": true, "workspaces": [ "apps/*", "packages/*" ], "scripts": { "build": "turbo run build", "dev": "turbo run dev --parallel", "test": "turbo run test" }, "devDependencies": { "turbo": "^LatestVersion" } }
Next, configure your turbo.json
file at the root of your monorepo to define pipeline tasks and their caching behavior.
// turbo.json { "$schema": "https://turbo.build/schema.json", "globalDependencies": [ "**/.env" // Example: any .env file change invalidates caches ], "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**"], "cache": true }, "test": { "dependsOn": [], "outputs": ["coverage/**"], "cache": true }, "lint": { "outputs": [] }, "dev": { "cache": false, // Dev servers are usually long-running, so no caching needed "persistent": true } } }
In this turbo.json
:
build
: Depends on^build
meaning it will first build any dependencies of the current package. It caches the output directoriesdist/**
and.next/**
.test
: No specific project dependencies for testing, but runs tests. Cachescoverage/**
.cache: true
forbuild
andtest
enables Turborepo's caching mechanism.
Implementing Remote Caching
Turborepo supports remote caching via an encrypted network connection to a dedicated Turborepo Remote Cache service (Vercel's product or self-hosted alternatives).
-
Vercel Integration (Easiest Way): If your monorepo is hosted on Vercel, remote caching is largely automatic. Just link your Turborepo with a Vercel team, and it will use Vercel's remote cache.
-
Self-Hosting (AWS S3, Google Cloud Storage, etc.): For self-hosting, you need to configure environment variables. For AWS S3:
# Set these in your CI/CD environment variables TURBO_REMOTE_CACHE_SIGNATURE_KEY="your-secret-key-for-signing-cache-requests" TURBO_REMOTE_CACHE_API="https://my-self-hosted-cache-endpoint.com" # Or direct S3 URL TURBO_REMOTE_CACHE_READ_ONLY=false # Set to true for read-only access in some CI stages
You would also need a backend service that implements Turborepo's remote cache API using a storage provider like S3. Turbo provides open-source examples and tools for this.
Example CI/CD Workflow (GitHub Actions):
# .github/workflows/ci.yml name: CI on: push: branches: - main pull_request: branches: - main jobs: build-and-test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: 18 - name: Install dependencies run: npm install # Or yarn install / pnpm install - name: Turborepo Remote Cache configuration env: TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }} # If using Vercel Remote Cache TURBO_TEAM: your-vercel-team-id # If using Vercel Remote Cache # Or for self-hosted: # TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} # TURBO_REMOTE_CACHE_API: ${{ secrets.TURBO_REMOTE_CACHE_API }} run: | # This command will automatically connect to the remote cache if env vars are set # or if linked to Vercel via `vercel link` echo "Configuring Turborepo for remote caching..." - name: Build affected projects run: npm run build # This will invoke `turbo run build` # Turborepo will automatically utilize the remote cache here. # If a build for a project has been cached, it will be restored. # Otherwise, it will build and then store the output in the cache. - name: Test affected projects run: npm run test # This will invoke `turbo run test` # Similar to build, tests will leverage caching.
On-Demand Builds in Action
Turborepo's on-demand build functionality works seamlessly with remote caching. When you run turbo run build
or turbo run test
, Turborepo performs the following steps:
- Graph Analysis: It constructs a dependency graph of your monorepo.
- Fingerprinting: For each task (e.g.,
build
forapps/web
), it calculates a unique "fingerprint" based on the task's inputs (source files, dependencies, environment variables,turbo.json
configuration, etc.). - Cache Lookup: It checks if this fingerprint exists in the local cache first, then in the remote cache.
- Execution or Restoration:
- If a cache hit occurs, it restores the outputs from the cache and marks the task as
cache hit
. - If no cache hit, it executes the task, then stores its outputs in both local and remote caches.
- If a cache hit occurs, it restores the outputs from the cache and marks the task as
- Affected Projects: When you make a change, Turborepo only re-fingerprints and re-evaluates the projects directly and indirectly affected by that change. Unaffected projects will likely find their tasks fully cached.
Practical Example:
Let's say your monorepo has apps/web
(React app) and packages/ui
(shared UI components).
- If you change a file in
apps/web
: Onlyapps/web
'sbuild
andtest
tasks will potentially run.packages/ui
's tasks will be pulled from the cache. - If you change a file in
packages/ui
: Bothpackages/ui
's tasks andapps/web
's tasks (becauseapps/web
depends onpackages/ui
) will potentially run. Other unrelated projects will be pulled from the cache.
This intelligent orchestration means CI/CD pipelines no longer rebuild everything on every commit, leading to significantly faster executions and reduced resource consumption.
Conclusion
Turborepo's remote caching and on-demand builds fundamentally transform how full-stack JavaScript monorepos are managed in CI/CD. By intelligently storing and retrieving build artifacts and selectively executing only affected tasks, Turborepo eliminates redundant work, drastically reducing build times and CI/CD costs. Embracing Turborepo is a strategic move for any team looking to accelerate their development cycles and enhance the efficiency of their full-stack monorepo workflows.