Turborepo cache miss on consecutive builds when `.next` folder exists, even with identical hash

Hi everyone! πŸ‘‹
I'm experiencing a strange caching issue with Turborepo and need help understanding if this is expected behavior or a bug.
## The Problem
Cache miss on consecutive builds when `.next` folder exists. Cache only works after manually deleting `.next`, even though the hash is identical.
## Environment
- **turbo:** 2.6.3
- **next:** 16.0.1
- **pnpm:** 9.14.2
- **Node:** 22.19.0
- **OS:** Windows 11
## How to Reproduce
```bash
# Step 1: Clean everything
rm -rf apps/*/.next apps/*/.turbo .turbo
# Step 2: First build
pnpm build
# Result: cache miss βœ… (expected)
# Step 3: Build again immediately (no changes)
pnpm build
# Expected: cache hit
# Actual: cache miss ❌
# Step 4: Delete .next folders
rm -rf apps/*/.next
# Step 5: Build again
pnpm build
# Result: cache hit βœ… (3.7s - FULL TURBO)
# Step 6: Build again immediately
pnpm build
# Expected: cache hit
# Actual: cache miss ❌ (38s)

The Strange Part

Same hash, different results:
After deleting .next:

web#build > cache hit, replaying logs 1a6444a677c8bb2c
Time: 3.714s >>> FULL TURBO

Immediate next build:

web#build > cache miss, executing 1a6444a677c8bb2c
Time: 38.036s

Hash 1a6444a677c8bb2c is identical both times! :thinking:

My Configuration

turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "ui": "tui",
  "tasks": {
    "//#biome-lint": {
      "inputs": [
        "apps/**/*.{ts,tsx,js,jsx}",
        "packages/**/*.{ts,tsx,js,jsx}",
        "biome.json",
        "packages/biome-config/biome.json"
      ],
      "outputs": []
    },
    "//#biome-format": {
      "cache": false,
      "outputs": []
    },
    "//#biome-check": {
      "cache": false,
      "outputs": []
    },
    "type-check": {
      "dependsOn": ["^type-check"],
      "inputs": ["**/*.ts", "**/*.tsx", "tsconfig.json", "tsconfig.*.json"],
      "outputs": []
    },
    "build": {
      "dependsOn": ["^build"],
      "inputs": [
        "$TURBO_DEFAULT$",
        "!Dockerfile*",
        "!*.dockerfile",
        "!.dockerignore",
        "!.next/**"
      ],
      "outputs": [
        ".next/**",
        "!.next/cache/**",
        "!.next/trace/**",
        "!.next/analyze/**",
        "!.next/*.log",
        "!.next/BUILD_ID",
        "!.next/standalone/**/.env*",
        "!.next/standalone/**/node_modules/.cache/**"
      ]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

next.config.ts (same for both apps)

import type { NextConfig } from 'next'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const Filename = fileURLToPath(import.meta.url)
const Dirname = dirname(Filename)
const nextConfig: NextConfig = {
  output: 'standalone',
  outputFileTracingRoot: join(Dirname, '../../'),
  transpilePackages: ['@repo/ui'],
  reactStrictMode: true,
  compress: true,
}
export default nextConfig

Project Structure

turborepo/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ web/
β”‚   β”‚   └── next.config.ts
β”‚   └── docs/
β”‚       └── next.config.ts
β”œβ”€β”€ packages/
β”‚   └── ui/
β”œβ”€β”€ turbo.json
└── package.json

What I’ve Tried

Attempt Result
Added !.next/** to inputs Still cache miss
Excluded .next/cache/** from outputs Still cache miss
Excluded multiple .next subfolders Still cache miss
Restarted turbo daemon Still cache miss
Cleared all caches Still cache miss
Deleted .next before build :white_check_mark: Cache works!

Current Workaround

{
  "scripts": {
    "build": "rimraf apps/*/.next && turbo run build"
  }
}

This works but defeats the purpose of caching.

Questions

  1. Is this expected behavior when output folder already exists?
  2. Is the output: 'standalone' config affecting cache behavior?
  3. Are my output exclusions causing issues?
  4. Is outputFileTracingRoot interfering with Turborepo caching?
  5. Anyone else experiencing this on Windows?
  6. Any proper fix besides deleting .next every time?

Impact

  • Every build takes 38s instead of 3.7s (10x slower)
  • Local development caching completely broken
  • CI/Docker works fine (fresh environment each time)
    Thanks for any help! :folded_hands:

## Current vs Expected Behavior
### Expected Behavior
When running `pnpm build` consecutively without any code changes:
- First build: cache miss (expected - no cache exists)
- Second build: **cache hit** (should restore from cache)
- Third build: **cache hit** (should restore from cache)
Build time with cache hit: ~3.7s (FULL TURBO)
### Current Behavior
When running `pnpm build` consecutively without any code changes:
- First build: cache miss βœ…
- Second build: **cache miss** ❌ (rebuilds everything)
- Third build: **cache miss** ❌ (rebuilds everything)
Build time: ~38s every time
### The Workaround That Works
If I delete `.next` folder before building:
- `rm -rf apps/*/.next`
- `pnpm build` β†’ **cache hit** βœ… (3.7s)
But immediately running `pnpm build` again β†’ **cache miss** ❌
### Summary
| Scenario | Expected | Actual |
|----------|----------|--------|
| `.next` doesn't exist | cache hit | βœ… cache hit |
| `.next` exists | cache hit | ❌ cache miss |
The cache only works when the output folder doesn't exist, even though the hash is identical.

## Steps to Reproduce
**Repository:** https://github.com/PSriVignesH/turborepo-remote-cache
```bash
# 1. Clone
git clone https://github.com/PSriVignesH/turborepo-remote-cache
cd turborepo-remote-cache
pnpm install
# 2. Login to Vercel Remote Cache
npx turbo login
# 3. Link to Remote Cache
npx turbo link
# 4. First build
pnpm build
# Result: cache will be hitβœ… 
# 5. Second build (no changes)
pnpm build
# Result: cache HIT for both βœ…
# 7. Make a small change to apps/web/app/page.tsx (add a comment or space)
# 8. Build again
pnpm build
# Expected: cache miss for web, cache HIT for docs
# Actual: cache miss for web βœ…, cache miss for docs ❌ (BUG!)
# 9. Build again (no changes)
pnpm build
# Expected: cache hit for both
# Actual: cache miss for both ❌ (BUG!)
# 10. Delete .next folders
rm -rf apps/*/.next
# 11. Build again
pnpm build
# Result: cache HIT for both βœ… (works!)
# 12. Build again (with .next existing)
pnpm build
# Expected: cache hit for both
# Actual: cache miss for both ❌ (BUG!)

# Complete Copy-Paste Ready
```markdown
## Project Information
**Repository:** https://github.com/PSriVignesH/turborepo-remote-cache
### Framework & Versions
| Package | Version |
|---------|---------|
| turbo | 2.6.3 |
| next | 16.0.1 |
| pnpm | 9.x |
| node | 22.19.0 |
### Environment
| Setting | Value |
|---------|-------|
| OS | Windows 11 (Git Bash/MINGW64) |
| Remote Cache | Vercel (enabled via `turbo login` & `turbo link`) |
| Build Mode | Standalone (`output: 'standalone'`) |
### Project Structure

turborepo-remote-cache/
β”œβ”€β”€ apps/
β”‚ β”œβ”€β”€ web/ # Next.js 16.0.1
β”‚ β”‚ β”œβ”€β”€ next.config.ts
β”‚ β”‚ └── app/page.tsx
β”‚ └── docs/ # Next.js 16.0.1
β”‚ β”œβ”€β”€ next.config.ts
β”‚ └── app/page.tsx
β”œβ”€β”€ packages/
β”‚ └── ui/ # Shared components
β”œβ”€β”€ turbo.json
β”œβ”€β”€ package.json
└── pnpm-workspace.yaml

### Key Configuration
**next.config.ts (both apps):**
```typescript
const nextConfig: NextConfig = {
  output: 'standalone',
  outputFileTracingRoot: join(Dirname, '../../'),
  transpilePackages: ['@repo/ui'],
}

turbo.json build task:

{
  "build": {
    "dependsOn": ["^build"],
    "inputs": ["$TURBO_DEFAULT$", "!.next/**"],
    "outputs": [".next/**", "!.next/cache/**"]
  }
}

Remote Cache Settings

  • Logged in via npx turbo login
  • Linked via npx turbo link
  • Using Vercel Remote Cache

# Turborepo Cache Bug: `output: 'standalone'` Breaks Cache Due to Long Symlink Paths

---

## Summary

Turborepo cache fails with Next.js `output: 'standalone'` because the standalone build creates symlinks in `.next/standalone/node_modules/` with paths too long for Windows/tar to handle. Cache writes fail silently with IO warnings, causing every subsequent build to miss cache despite identical hashes.

---

## Environment

| Tool | Version |
|------|---------|
| OS | Windows 11 (Git Bash/MINGW64) |
| Node | v20.x |
| pnpm | 9.x |
| Turborepo | 2.6.3 |
| Next.js | 16.0.7 (Turbopack) |

---

## The Bug

| Configuration | Cache Works? | Error |
|---------------|--------------|-------|
| Without `output: 'standalone'` | βœ… Works perfectly | None |
| With `output: 'standalone'` | ❌ Always fails | IO error: path too long |

**Everything else remains identical. Only adding `standalone` breaks cache.**

---

## Error Message

WARNING IO error: provided value is too long when setting link name for apps/docs/.next/standalone/node_modules/typescript
WARNING IO error: provided value is too long when setting link name for apps/web/.next/standalone/node_modules/typescript


---

## Reproduction

### Step 1: Create Fresh Turborepo Project

```bash
npx create-turbo@latest turbo-bug-proof
cd turbo-bug-proof
pnpm install

Step 2: Test Without Standalone (Works :white_check_mark:)

pnpm build  # miss (expected)
pnpm build  # hit βœ…
pnpm build  # hit βœ…

Output:

Tasks:    2 successful, 2 total
Cached:    2 cached, 2 total
Time:    655ms >>> FULL TURBO

Step 3: Add Standalone to Both Next.js Apps

apps/web/next.config.ts:

import type { NextConfig } from 'next'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'

const Filename = fileURLToPath(import.meta.url)
const Dirname = dirname(Filename)

const nextConfig: NextConfig = {
  output: 'standalone',
  outputFileTracingRoot: join(Dirname, '../../'),
  transpilePackages: ['@repo/ui'],
  reactStrictMode: true,
  compress: true,
}

export default nextConfig

apps/docs/next.config.ts: (Same configuration)

Step 4: Test With Standalone (Fails :cross_mark:)

pnpm build  # miss (expected, config changed)
pnpm build  # miss ❌ (BUG! should hit)

Output:

Tasks:    2 successful, 2 total
Cached:    0 cached, 2 total
Time:    20.145s

>>> ...Finishing writing to cache...
WARNING  IO error: provided value is too long when setting link name for apps/docs/.next/standalone/node_modules/typescript
WARNING  IO error: provided value is too long when setting link name for apps/web/.next/standalone/node_modules/typescript

Evidence

1. Cache Miss with Identical Hash

Build 1: cache miss, executing 9e10e10a45480de7
Build 2: cache miss, executing 9e10e10a45480de7  ← SAME HASH!

2. IO Error During Cache Write

...Finishing writing to cache...
WARNING  IO error: provided value is too long when setting link name for apps/docs/.next/standalone/node_modules/typescript
WARNING  IO error: provided value is too long when setting link name for apps/web/.next/standalone/node_modules/typescript

3. Cache Files Exist But Are Incomplete

$ ls -la .turbo/cache/
9e10e10a45480de7.tar.zst      # Cache file exists
9e10e10a45480de7-meta.json
ee2330f1de02079d.tar.zst
ee2330f1de02079d-meta.json

4. Dry-Run Shows Cached = True

$ pnpm turbo build --dry-run

web#build
  Hash                           = 9e10e10a45480de7
  Cached (Local)                 = true   ← SAYS CACHED!

But actual pnpm build still shows cache miss with IO warnings!


Root Cause

The .next/standalone/node_modules/ directory contains symlinks that point to paths too long for:

  1. Windows path limits (260 characters by default)
  2. tar archive link name limits

Turborepo uses .tar.zst for caching. When it tries to archive the standalone output, the symlinks fail to be stored properly due to path length limitations.

The cache write fails silently (only shows warning), so subsequent builds can’t restore from cache.


Impact

  • output: 'standalone' is required for Docker deployments
  • Turborepo cache is completely broken with standalone on Windows
  • No caching benefit for Docker builds
  • This defeats the entire purpose of Turborepo for production deployments

Expected Behavior

pnpm build  # miss (first build)
pnpm build  # hit βœ… (cached)

Actual Behavior

pnpm build  # miss + IO error warning
pnpm build  # miss + IO error warning ❌ (never hits cache)

Attempted Fixes (All Failed)

Attempt Result
Exclude !.next/standalone/** from outputs :cross_mark: Still fails
Exclude !.next/**/*.nft.json from outputs :cross_mark: Still fails
Use specific inputs instead of $TURBO_DEFAULT$ :cross_mark: Still fails
Use process.cwd() instead of import.meta.url :cross_mark: Still fails
Add explicit cacheDir to turbo.json :cross_mark: Still fails

Configuration Files

turbo.json

{
  "$schema": "https://turborepo.com/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": [".next/**", "!.next/cache/**"]
    }
  }
}

next.config.ts

import type { NextConfig } from 'next'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'

const Filename = fileURLToPath(import.meta.url)
const Dirname = dirname(Filename)

const nextConfig: NextConfig = {
  output: 'standalone',
  outputFileTracingRoot: join(Dirname, '../../'),
  transpilePackages: ['@repo/ui'],
  reactStrictMode: true,
  compress: true,
}

export default nextConfig

Possible Fixes

  1. Handle long symlink paths properly in tar archive on Windows
  2. Use relative paths for symlinks instead of absolute paths
  3. Exclude symlinks from cache archive and regenerate on restore
  4. Skip caching standalone/node_modules with clear warning/documentation
  5. Enable Windows long path support in tar implementation

Workaround

Currently no workaround that preserves both:

  • :white_check_mark: Turborepo caching
  • :white_check_mark: Standalone output for Docker

The only option is to disable standalone locally and enable only in CI, but this means CI builds cannot benefit from remote cache.


Related Links


Summary

This is a critical bug affecting all Turborepo users who:

  1. Use Windows for development
  2. Deploy Next.js apps with Docker (requires standalone)
  3. Expect Turborepo caching to work

The root cause is IO errors when caching symlinks with long paths in the standalone output. The cache write fails silently, causing every build to miss cache despite identical hashes.

Hey, @srivignesh3555-7865! Welcome to the Vercel Community :waving_hand:

I can see Anthony replied to you on X! Let us know if we can help with anything else.
https://x.com/SRIVIGNESHFSDEV/status/1997967000849830398?s=20

Hey, Pauline. Yeah, I have got the reply from Anthony in X. I’m waiting for the solution of this bug or waiting to hear from Anthony or your team if there is any workaround I can add to code that will fix this. Thank you

1 Like