Skip to content

fix(typegen): normalize %5F to _ before private folder check in dev bundler#93725

Open
sleitor wants to merge 1 commit intovercel:canaryfrom
sleitor:fix-93700
Open

fix(typegen): normalize %5F to _ before private folder check in dev bundler#93725
sleitor wants to merge 1 commit intovercel:canaryfrom
sleitor:fix-93700

Conversation

@sleitor
Copy link
Copy Markdown
Contributor

@sleitor sleitor commented May 9, 2026

Summary

When Turbopack is used in dev mode, it URL-encodes _ as %5F in directory names when reporting file paths to the dev bundler. The private folder exclusion check:

if (normalizedPageName.includes('/_')) continue

works correctly in webpack dev mode (literal _ in paths), but fails in Turbopack mode because '/%5Ffoo'.includes('/_') is false.

Root Cause

In setup-dev-bundler.ts, when collecting app routes and layouts for the typegen manifest, Turbopack-encoded paths like /%5Ffoo/layout bypass the private folder filter. This causes:

  1. Layout routes in _-prefixed directories leak into dev types — e.g. app/_foo/layout.tsx appears in dev .next/dev/types/routes.d.ts as "/%5Ffoo" in LayoutRoutes
  2. Type mismatch — the production build correctly excludes private folders via ignorePartFilter: (part) => part.startsWith('_') in collectAppFiles, so .next/types/routes.d.ts does NOT have "/%5Ffoo" or "/_foo". The dev validator imports both files and fails with:
    Type '"/%5Ffoo"' is not assignable to type 'LayoutRoutes'
    

Fix

Normalize %5F_ in normalizedPageName before the private folder check, matching the production build's behavior.

-const normalizedPageName = normalizePathSep(pageName)
+// Normalize URL-encoded underscores (Turbopack encodes '_' as '%5F' in
+// directory names). This ensures private folders (directories starting
+// with '_') are correctly excluded from routing, matching the behavior
+// of the production build (which uses ignorePartFilter: part.startsWith('_')).
+const normalizedPageName = normalizePathSep(pageName).replace(/%5F/g, '_')

The same %5F_ normalization pattern is already used at line 682 for app route/page collection in the same function, and in route-discovery.ts for the production build.

Testing

  • Regression verified against the existing includes('/_') private folder filter
  • ESLint + lint-staged pass ✅
  • The fix only affects the normalizedPageName used for the private folder check and the layout route string — the internal bundler identifiers (in appPaths) are unaffected, as they're normalized separately at line 682

Fixes #93700

…der check

Turbopack URL-encodes '_' as '%5F' in directory names when reporting
file paths to the dev bundler. The existing check:

  if (normalizedPageName.includes('/_')) continue

was correctly excluding private folders (directories starting with '_')
in webpack dev mode (where file paths use literal underscores), but
failed in Turbopack mode because '/%5Ffoo'.includes('/_') is false.

This caused two bugs when using Turbopack dev mode:
1. Private folder layouts (e.g. app/_foo/layout.tsx) leaked into the
   dev typegen LayoutRoutes as '/%5Ffoo' instead of being excluded.
2. The encoded route '/%5Ffoo' was incompatible with production types
   (which exclude '_'-prefixed directories via ignorePartFilter), causing
   TypeScript errors like:
     Type '"/%5Ffoo"' is not assignable to type 'LayoutRoutes'

Fix: normalize '%5F' to '_' in normalizedPageName before the private
folder check, matching the production build's ignorePartFilter behavior.

Fixes vercel#93700
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Layouts for paths that start with underscore (%5F) produces error for dev types of LayoutRoutes

1 participant