remix-run/remix · error · Error

Detected a route map cycle at "${currentPath}" via "${existi

Error message

Detected a route map cycle at "${currentPath}" via "${existingPath}".

What it means

The routes map is traversed recursively with a `seen` map; if the same object reference is reached twice, your config contains a cycle (direct or indirect self-reference) and the worker aborts instead of infinite-looping.

Source

Thrown at packages/cli/src/lib/load-route-map-worker.ts:49

  }

  return normalizeRouteGroup(routeModule.routes, [], new Map())
}

function normalizeRouteGroup(
  value: unknown,
  parentSegments: string[],
  seen: Map<object, string>,
): RawRouteTreeNode[] {
  if (!isPlainObject(value)) {
    let location = parentSegments.length === 0 ? 'routes' : parentSegments.join('.')
    throw new Error(`Invalid route map value at "${location}". Expected a nested route object.`)
  }

  let existingPath = seen.get(value)
  let currentPath = parentSegments.length === 0 ? 'routes' : parentSegments.join('.')
  if (existingPath != null) {
    throw new Error(`Detected a route map cycle at "${currentPath}" via "${existingPath}".`)
  }

  seen.set(value, currentPath)

  try {
    return Object.entries(value).map(([key, entryValue]) => {
      let nameSegments = [...parentSegments, key]
      let name = nameSegments.join('.')
      let routeLeaf = readRouteLeaf(entryValue)

      if (routeLeaf !== undefined) {
        return {
          children: [],
          key,
          kind: 'route',
          method: routeLeaf.method,
          name,
          pattern: routeLeaf.pattern,

View on GitHub (pinned to 9696913134)

Solutions

  1. Find the reported path (e.g. 'routes.admin') and remove the self/indirect reference
  2. If reusing a subtree, clone it with structuredClone or spread (`{ ...shared }`) instead of referencing the same object
  3. Avoid `obj.child = obj` patterns when assembling groups programmatically

Example fix

// before
let admin = {}; admin.self = admin
export let routes = { admin }
// after
export let routes = { admin: {} }
Defensive patterns

Strategy: validation

Validate before calling

function assertAcyclic(node, seen = new Set()) {
  if (seen.has(node)) throw new Error('cycle')
  seen.add(node)
  for (let k in node) if (typeof node[k] === 'object') assertAcyclic(node[k], seen)
  seen.delete(node)
}

Prevention

When it happens

Trigger: An object in the routes map referencing itself: `let group = { nested: group }` or two objects referencing each other via getter/spread chains that preserve identity.

Common situations: Building route groups programmatically with variables and accidentally assigning a parent into its own children; using shared constant objects that get inserted at multiple nesting levels via assignment (fine with spread, a cycle with direct reference).

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/603aa8017793ca71. Report an issue: GitHub.