Alright, let’s talk about the part of Turborepo that will save you from throwing your laptop out the window: caching. If you’re not using its caching superpowers for your TypeScript builds, you’re essentially paying for a sports car and never taking it out of first gear. It’s the difference between waiting for a full tsc --build every single time and, well, not doing that.

The core idea is gloriously simple. Turborepo runs a function (a task in its parlance) and calculates a hash based on the inputs—your source files, tsconfig.json, environment variables, and any other dependency you specify. If it’s seen that hash before, it skips the arduous work of running the task and just plops the previous output (the compiled JS, .d.ts files, etc.) back into place from its cache. It’s like a time machine for your dist directory.

The Anatomy of a Cache Key

The magic is in what you tell Turborepo to consider. By default, it’s pretty smart, but you’ll want to be explicit. This happens in your turbo.json pipeline. For a TypeScript build task, it might look like this:

{
  "pipeline": {
    "build": {
      "outputs": ["dist/**", ".tsbuildinfo"],
      "dependsOn": ["^build"],
      "inputs": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "tsconfig.json",
        "tsconfig.*.json"
      ]
    }
  }
}

Let’s break this down. The inputs array is the recipe for our hash. If any of these files change, the cache key changes, and the task runs again. Notice I’ve included tsconfig.*.json—this is a classic “gotcha.” If you have a tsconfig.production.json and forget to include it, your production builds will get cache hits from your development builds, which is a one-way ticket to bizarre, hard-to-reproduce bugs.

The outputs array is equally critical. This is what Turborepo will restore from the cache. For tsc, you must include the .tsbuildinfo file. This file is tsc’s internal memoization state. If you restore the dist files from cache but don’t restore this file, tsc on the next run will get horribly confused—it will see output files that are newer than its last memory of them and might do a completely unnecessary rebuild. It’s like giving an amnesiac chef a fully prepared meal and asking them to cook it again; chaos ensues.

The .tsbuildinfo File: Cache Inception

I just mentioned it, but this deserves its own spotlight. The interaction between Turborepo’s cache and TypeScript’s incremental compilation (via the .tsbuildinfo file) is a thing of beauty when done right and a source of silent failures when done wrong.

Think of it as cache-ception. Turborepo is the macro-cache, and the .tsbuildinfo enables TypeScript’s micro-cache. The rule is simple: They must be kept in sync. If your Turborepo cache restores the dist directory, it must also restore the accompanying .tsbuildinfo file. This is why our outputs configuration above is so explicit. If you omit it, you’re breaking the chain of trust between the two systems.

The “Why Did This Not Cache?!” Debugging Guide

You’ll run turbo build and it will run everything, and you’ll scream, “BUT I ONLY CHANGED A COMMENT!”. Here’s your checklist:

  1. Glob Patterns: Your inputs glob might not be capturing the file. Be specific. src/**/*.ts is good, but maybe you have scripts in a scripts/** directory? Add it.
  2. Environment Variables: If your build uses process.env.SOME_VAR to change behavior, you need to tell Turborepo to include it in the hash. Add "env": ["SOME_VAR"] to your task config in turbo.json. Otherwise, a change to that var won’t bust the cache.
  3. Other Config Files: Does your build read from package.json? Maybe a eslint.config.js? You need to add them to inputs. Turborepo isn’t psychic. A good pattern is:
"inputs": [
  "src/**/*.ts",
  "src/**/*.tsx",
  "tsconfig.json",
  "tsconfig.*.json",
  "package.json",
  "eslint.config.js" // if your build lints first
]
  1. The Nuclear Option: Sometimes, you just need to see what the heck is going on. Run npx turbo build --dry-run=json. This will show you the calculated hash for each task and whether it’s a cache hit or miss. It’s the ultimate debugging tool for your pipeline.

Beyond tsc: esbuild and swc

Most modern monorepos use faster bundlers/compilers like esbuild or swc. The same principles apply, but the outputs might differ. For esbuild, you likely don’t have a .tsbuildinfo equivalent, but you might have multiple output patterns. Be just as meticulous.

// For an esbuild task that outputs multiple bundles
"outputs": ["dist/**", "build/**"]

The golden rule remains: your outputs configuration must perfectly mirror what the underlying tool produces on a fresh run. If the tool creates it, Turborepo needs to manage it. Get this right, and you’ll witness builds that finish in milliseconds. Get it wrong, and you’ll be that person angrily staring at a progress bar, wondering what you did to deserve this. Let’s not be that person.