cache-invalidation content-stale turbo-inputs markdown monorepo

Turborepo Cache Invalidation Failing for Markdown Content Changes

Problem

Turborepo cache invalidation was not working for content changes in the Astro monorepo. New markdown (.md) content files added to apps/sendy-hq/src/content/overnight/ were not appearing in deployed builds, displaying stale cached content instead.

The sendy-hq site showed only 1 overnight report after deploy, when ~10 new reports had been added.

Investigation

  1. Symptom identification: After deploying new content to sendy-hq, only 1 overnight report appeared on the live site despite multiple new .md files being added locally.

  2. File verification: Confirmed that all new .md content files existed in the repository at the correct location (apps/sendy-hq/src/content/overnight/).

  3. Build pipeline analysis: Examined the GitHub Actions workflow which used pnpm turbo build --filter='./apps/*[HEAD^1]' to detect and rebuild only changed applications using Turborepo’s change detection.

  4. Cache detection: Realized that Turbo’s default input configuration did not explicitly include markdown (.md) files in its cache invalidation hash calculation.

Root Cause

Turborepo’s default hashing mechanism for determining cache validity does not automatically include all file types in cache keys. When only .md content files changed without corresponding changes to .ts, .astro, or configuration files, Turbo’s computed hash remained identical to previous builds. This caused Turbo to serve the cached dist/ directory from a prior build instead of rebuilding with the new content.

Solution

Updated turbo.json to explicitly include all content files in the build task’s inputs:

"build": {
  "dependsOn": ["^build"],
  "inputs": ["src/**", "public/**", "*.ts", "*.mts", "*.json", "*.astro"],
  "outputs": ["dist/**"]
}

The "src/**" glob pattern ensures that any file changes under the src/ directory — including markdown content files — trigger cache invalidation and force a rebuild.

Verification

After applying the fix to turbo.json, a deployment was forced via GitHub Actions workflow_dispatch trigger. The build process used the updated inputs configuration, which properly detected the content changes and rebuilt the affected applications. All overnight reports subsequently appeared correctly in the deployed site.

Prevention

  • Document all content file types that trigger builds in turbo.json. When Astro processes .md files into static HTML, those changes must be explicitly listed in the inputs array or cache misses occur silently.
  • Pre-deployment checklist: After adding new file types (.md, .json, .yaml, templates), verify they’re in turbo.json inputs for affected tasks before merging.
  • Test cache behavior locally before deploying: pnpm turbo build --filter=<slug> --force (fresh) vs without --force (cached) should produce identical output.

What to Check When Adding New File Types

  1. Content files (.md, .mdx, .yaml, .toml): Must be in inputs if read during build.
  2. Config files (.ts, .json, .mts): Check if the build task sources them.
  3. Static assets (public/**): Verify inputs includes them.
  4. CI context: Test in GitHub Actions, not just locally. Turborepo’s cache directory behavior differs between local and CI runners.

Best Practices

  1. Explicit over implicit — List all meaningful patterns rather than relying on defaults.
  2. Separate build tasks by scope — Content-heavy sites (blogs, docs) may need different inputs than product sites.
  3. Include root configuration — Add turbo.json, package.json, and tsconfig.json to inputs to invalidate cache when build tooling changes.

Debugging Checklist

  • Verify task inputs in turbo.json include all relevant file types
  • Force a fresh build: pnpm turbo build --filter=<slug> --force and compare output
  • Check for “cache hit” vs “cache miss” in verbose build logs
  • Test content-only changes: modify only a .md file and rebuild; cache should miss
  • Validate glob patterns against your file tree
  • Inspect .turbo/ cache directory for stale entries
  • Check if .gitignore or .turbignore patterns exclude source files
  • Review CI vs local — GitHub Actions may cache Turbo state across runs