Skip to content

fix: auto-restart dev server when .next directory is deleted#91142

Draft
sokra wants to merge 9 commits intocanaryfrom
sokra/persisting-removed
Draft

fix: auto-restart dev server when .next directory is deleted#91142
sokra wants to merge 9 commits intocanaryfrom
sokra/persisting-removed

Conversation

@sokra
Copy link
Member

@sokra sokra commented Mar 10, 2026

What?

Auto-restart the Next.js dev server when the .next directory is deleted while the server is running, instead of serving broken pages with cryptic ENOENT errors.

Why?

When a developer deletes the .next directory during active development (e.g., to clear caches or fix a corrupt build state), the dev server enters a broken state. Requests fail with unhelpful ENOENT errors for manifests and compiled files (pages-manifest.json, routes-manifest.json, _buildManifest.js, [turbopack]_runtime.js). The developer has to manually restart the dev server to recover.

How?

Detection via fs.watch (packages/next/src/server/lib/router-server.ts):

  • After dev server initialization, set up an fs.watch watcher on the distDir (.next/dev in development)
  • When the watcher fires (file system change or error), check if the distDir still exists using existsSync
  • If the directory is missing, log a warning and call process.exit(RESTART_EXIT_CODE) (exit code 77)
  • The parent process in next-dev.ts already handles exit code 77 by restarting the server (same mechanism used for config file changes)
  • A shared restartOnDistDirRemoval helper handles both the watch callback and error event, avoiding code duplication

Key design decisions:

  • Uses fs.watch for instant detection — the server restarts as soon as the directory is removed, without waiting for a request
  • The existsSync guard inside the callback prevents false positives from other filesystem events (e.g. files being written to .next)
  • The warning message uses config.distDirRoot (.next) rather than config.distDir (.next/dev) so it matches what the developer actually deleted
  • If the watcher can't be set up (e.g. distDir doesn't exist yet), the error is silently ignored — the server will still work, just without automatic restart on deletion

Test (test/development/app-dir/delete-dot-next-dir/):

  • Uses it.each to parametrize both app router and pages router recovery after .next deletion
  • Verifies: page loads → delete .next → watcher triggers restart → CLI shows warning + "Ready in" → page loads again after restart
  • Uses turbopackFileSystemCacheForDev: true to also exercise the persistent cache code path
  • Runs with TURBO_ENGINE_IGNORE_DIRTY: '1' since the test environment has a dirty git repo

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Mar 10, 2026

Failing test suites

Commit: 4890499 | About building and testing Next.js

pnpm test-dev test/development/app-dir/delete-dot-next-dir/delete-dot-next-dir.test.ts (job)

  • delete-dot-next-dir > should recover after .next is deleted (app router) (DD)
Expand output

● delete-dot-next-dir › should recover after .next is deleted (app router)

expect(received).toContain(expected) // indexOf

Expected substring: "The .next directory was removed while the dev server was running. Restarting..."
Received string:    "▲ Next.js 16.2.0-canary.91 (Turbopack)
- Local:         http://localhost:34323
- Network:       http://135.181.51.11:34323
✓ Ready in 196ms
Creating turbopack project {
  dir: '/tmp/next-install-596be4a8febd5136c8d1a95add9d8e32506e1ceac0d7324581fcef94d185f3f0',
  testMode: true
}·
  We detected TypeScript in your project and created a tsconfig.json file for you.
- Experiments (use with caution):
  ✓ strictRouteTypes (enabled by `__NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES`)·
 GET /app 200 in 848ms (next.js: 742ms, application-code: 106ms)
Persisting failed: Unable to write SST file 00000005.sst·
Caused by:
    No such file or directory (os error 2)
"

  40 |       await retry(async () => {
  41 |         const cliOutput = next.cliOutput
> 42 |         expect(cliOutput).toContain(
     |                           ^
  43 |           'The .next directory was removed while the dev server was running. Restarting...'
  44 |         )
  45 |         expect(cliOutput).toContain('Ready in')

  at toContain (development/app-dir/delete-dot-next-dir/delete-dot-next-dir.test.ts:42:27)
  at fn (lib/next-test-utils.ts:861:20)
  at development/app-dir/delete-dot-next-dir/delete-dot-next-dir.test.ts:40:7

pnpm test-dev-turbo test/development/app-dir/hmr-intercept-routes/hmr-intercept-routes.test.ts (turbopack) (job)

  • hmr-intercept-routes > should update intercept routes via HMR (DD)
Expand output

● hmr-intercept-routes › should update intercept routes via HMR

page.waitForSelector: Timeout 10000ms exceeded.
Call log:
  - waiting for locator('#default-intercept') to be visible

  519 |
  520 |     return this.startChain(async () => {
> 521 |       const el = await page.waitForSelector(selector, {
      |                             ^
  522 |         timeout,
  523 |         state,
  524 |       })

  at waitForSelector (lib/browsers/playwright.ts:521:29)
  at Playwright._chain (lib/browsers/playwright.ts:651:23)
  at Playwright._chain [as startChain] (lib/browsers/playwright.ts:632:17)
  at Playwright.startChain [as waitForElementByCss] (lib/browsers/playwright.ts:520:17)
  at Object.waitForElementByCss (development/app-dir/hmr-intercept-routes/hmr-intercept-routes.test.ts:38:21)

sokra added 7 commits March 10, 2026 12:31
Adds a development test that verifies the behavior when the .next directory
is deleted while the Turbopack dev server is running. The test confirms that
both app router and pages router routes return 500 errors and appropriate
ENOENT error messages appear in the CLI output.
…ull CLI snapshots

Restructure fixture to use /app -> /app/other and /pages -> /pages/other routes.
Test app and pages router deletion behavior separately with beforeEach/afterEach
isolation. Add full CLI output snapshots alongside deduplicated error snapshots.
Replace environment-specific absolute paths to the Next.js package
directory with NEXT_DIR placeholder so snapshots are portable across
different machines and CI environments.
- Replace fragile full CLI output snapshots with stable deduplicated
  error message arrays (first-line only, sorted, deduped)
- Use next.testDir directly for path normalization instead of
  next.normalizeTestDirContent wrapper
- Snapshots verified without NEXT_SKIP_ISOLATE to test real module
  resolution paths
Enable persistent filesystem cache via turbopackFilesystemCacheInDev
config option. Add 5s delay after error detection to let cache
read/write errors settle. Use retry for initial page loads since the
server may need time to rebuild after a previous test deleted .next.
- Fix config key: turbopackFileSystemCacheForDev (not turbopackFilesystemCacheInDev)
- Set TURBO_ENGINE_IGNORE_DIRTY=1 via env option so filesystem cache
  works even when the git repo is dirty
- Wait 5s after error detection for persistent cache errors to settle
- Use retry for initial page loads since server may need time to rebuild
Detect .next directory deletion at request time and trigger an automatic
restart using the existing exit-code-77 restart mechanism, instead of
serving broken pages with cryptic ENOENT errors.
@sokra sokra force-pushed the sokra/persisting-removed branch from 7ce071e to f317d1c Compare March 10, 2026 12:06
Comment on lines +208 to +215
// If the distDir was deleted, restart the dev server instead of
// serving broken pages with cryptic ENOENT errors.
if (opts.dev && !existsSync(path.join(opts.dir, config.distDir))) {
Log.warn(
`The ${(config as NextConfigComplete).distDirRoot || config.distDir} directory was removed while the dev server was running. Restarting...`
)
process.exit(RESTART_EXIT_CODE)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to use one of the filesystem watchers we have, instead of adding work to every request?

Replace the synchronous existsSync check on every request with a
fs.watch watcher on the distDir. This avoids adding work to every
request and instead reacts to filesystem events when the directory
is actually deleted.

Addresses review feedback from bgw on #91142.
@vercel
Copy link
Contributor

vercel bot commented Mar 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v0-next-js Error Error Open in v0 Mar 10, 2026 9:46pm

Extract shared restart logic into restartOnDistDirRemoval helper,
use it.each to parametrize app/pages test cases, and remove unused
fixture files that were only needed for the old per-request approach.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants