Skip to content

Migration CLI mishandles remix-flat-routes folder route pattern (route.tsx) #14

@coji

Description

@coji

Description

When running npx migrate-auto-routes on a project that uses the remix-flat-routes folder route pattern (route.tsx inside sub-folders), route files are misclassified as colocated files and placed under +-prefixed folders, making them invisible to the router.

Steps to Reproduce

Run npx migrate-auto-routes on a project with the following structure:

Input (remix-flat-routes)

app/routes/
├── demo+/
│   ├── _layout/route.tsx          ← route file
│   ├── _index/route.tsx           ← route file
│   ├── about/route.tsx            ← route file
│   ├── conform.nested-array/
│   │   ├── route.tsx              ← route file
│   │   ├── components/            ← colocated
│   │   ├── schema.ts             ← colocated
│   │   └── faker.server.ts       ← colocated
│   └── ...

Actual Output (buggy)

app/routes/
├── demo/
│   ├── +_layout/route.tsx         ← folder gets + prefix → ignored by router!
│   ├── +_index/route.tsx          ← same
│   ├── +about/route.tsx           ← same
│   ├── +conform.nested-array/     ← same
│   │   ├── route.tsx
│   │   ├── components/
│   │   └── ...

The CLI's snapshot comparison catches the mismatch and rolls back, so no data is lost — but the migration cannot complete.

Root Cause Analysis

I found 5 interrelated bugs. Fixes for all 5 are in my fork: coji/react-router-auto-routes@fix/folder-route-migration. All 124 existing tests pass.

Bug 1: isColocatedFile() misclassifies route files as colocated

File: src/migration/fs-helpers.ts

isColocatedFile() checks whether a path inside a +-suffixed folder contains a "regular" (non-+, non-special) directory segment, and if so, marks the file as colocated. However, in remix-flat-routes, demo+/about/route.tsx has about/ as a route folder, not a colocated directory.

Example: demo+/about/route.tsx

  • demo+ → has + → flat files convention
  • about → no +, no _, no ()misclassified as colocated

This causes scanRouteModules() to find only 14 routes instead of the expected 39.

Fix: When only one regular directory segment exists after the last + folder and the file is a route entry (route.tsx, index.tsx, etc.), return false.

Bug 2: scanRouteModules() generates incorrect route IDs for route.tsx

File: src/migration/route-scanner.ts

Route IDs for route.tsx files include an extra /route segment (e.g., app/routes/demo+/_index/route instead of app/routes/demo+/_index), causing createRoutesFromFolders to fail at building the route tree.

Additionally, createRouteId() strips dot-segments as file extensions, so ($lang).biography becomes ($lang).

Fix: When basename is route, use path.dirname() directly as the route ID instead of createRouteId().

Bug 3: normalizeSnapshotRouteFilePath() doesn't normalize folder route paths

File: src/migration/normalizers.ts

The before/after snapshot comparison fails because routes/demo/about/route.tsx (before) doesn't match routes/demo/about.tsx (after). The _layout segment has skip logic but route does not.

Fix: Add route to the segment skip logic, similar to how _layout is handled.

Bug 4: +types/route import specifiers are not rewritten

File: src/migration/import-rewriter.ts

When route.tsx is converted to a flat file (e.g., about.tsx), ./+types/route should become ./+types/about. Since +types/ is a virtual directory generated by React Router, filesystem-based resolution doesn't work.

Fix: Add rewriteTypesRouteSpecifier() that detects +types/route specifiers and rewrites them based on the target filename.

Bug 5: index.ts extension in imports is not stripped

File: src/migration/import-rewriter.ts

Source code with explicit .ts extension imports like ./components/index.ts keeps the extension after migration, causing TypeScript errors without allowImportingTsExtensions.

Fix: Add stripTsExtension() that removes /index.ts and /index.tsx suffixes from import specifiers.

Files Changed

  1. src/migration/fs-helpers.tsisColocatedFile() fix
  2. src/migration/route-scanner.ts — route ID generation for route.tsx
  3. src/migration/normalizers.tsroute segment skip in snapshot normalization
  4. src/migration/import-rewriter.tsrewriteTypesRouteSpecifier() and stripTsExtension()
  5. src/migration/migrate.ts — route source path exclusion filter in collectColocatedMappings

Branch with Fix

https://github.com/coji/react-router-auto-routes/tree/fix/folder-route-migration

Happy to open a PR if you'd like.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions