Skip to content

migrate-auto-routes does not support @react-router/fs-routes ._index convention #16

@coji

Description

@coji

Description

migrate-auto-routes only supports the remix-flat-routes index convention (.index suffix) and does not handle the @react-router/fs-routes convention (._index suffix). This causes a path conflict error when migrating projects that use React Router v7's built-in flatRoutes.

Steps to Reproduce

Run npx migrate-auto-routes on a project using @react-router/fs-routes with the following structure:

Input (@react-router/fs-routes)

app/routes/
├── newsletter.tsx            ← layout route (has <Outlet />)
├── newsletter._index.tsx     ← index child route
├── newsletter.subscribe.ts
├── newsletter.error.tsx
├── products.tsx              ← layout route
├── products._index/
│   └── route.tsx             ← index child route (folder-based)
├── products.detail/
│   └── route.tsx
└── ...

And app/routes.ts:

import { flatRoutes } from '@react-router/fs-routes'
export default flatRoutes({ ignoredRouteFiles: ['**/*.test.{js,jsx,ts,tsx}'] })

Error

$ npx migrate-auto-routes app/routes app/new-routes
❌ Path "newsletter" defined by route "app/routes/newsletter" conflicts with route "app/routes/newsletter._index"

Root Cause Analysis

Two locations in src/migration/create-routes-from-folders.ts:

1. getParentRouteIds() (line 133) — parent resolution misses ._index

The dot-notation parent lookup only strips .index (remix-flat-routes convention):

if (!parentRouteId && childRouteId.endsWith(DOT_INDEX_SUFFIX)) { // DOT_INDEX_SUFFIX = '.index'
  const dotNotationParentId = childRouteId.slice(0, -DOT_INDEX_SUFFIX.length)

For routes/newsletter._index, stripping .index yields routes/newsletter._ — which doesn't match the parent routes/newsletter. The ._index suffix is never checked.

2. isIndexRouteId() (line 216) — does not recognize ._index

function isIndexRouteId(routeId: string): boolean {
  return (
    routeId === 'index' ||
    routeId.endsWith('/index') ||
    routeId.endsWith(DOT_INDEX_SUFFIX) // '.index' only
  )
}

routes/newsletter._index is not recognized as an index route, so it generates uniqueKey "newsletter" instead of "newsletter?index", conflicting with the layout route.

Context

  • remix-flat-routes uses .index (e.g., newsletter.index.tsx)
  • @react-router/fs-routes uses ._index (e.g., newsletter._index.tsx) — see source: routeId.endsWith("_index")
  • Since @react-router/fs-routes is the official React Router v7 file routing solution, supporting ._index would expand the migration tool's reach beyond remix-flat-routes users

Suggested Fix

Add ._index handling in both locations. For example:

constants.ts: Add DOT_UNDERSCORE_INDEX_SUFFIX = '._index'

isIndexRouteId():

routeId.endsWith(DOT_INDEX_SUFFIX) ||
routeId.endsWith(DOT_UNDERSCORE_INDEX_SUFFIX)

getParentRouteIds(): Duplicate the existing .index parent-lookup block for ._index:

if (!parentRouteId && childRouteId.endsWith(DOT_UNDERSCORE_INDEX_SUFFIX)) {
  const dotNotationParentId = childRouteId.slice(0, -DOT_UNDERSCORE_INDEX_SUFFIX.length)
  if (routeIdMap.has(dotNotationParentId)) {
    parentRouteId = dotNotationParentId
  }
}

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