Repository: vercel/next.js
Pull request: [ftl] Include cause of route types

Targetted at debugging (too) complex route types.

Helps debugging which route type was caused by which route file. This is especially helpful with non-trivial routes (route groups, breadcrumbs) and redirects/rewrites. 

Redirect/rewrites won't have a helpful cause. For now the most helfpul info for me was knowing which file route it belongs to and if it's a redirect or not.

First commit is a refactor getting rid of intermediate arrays in favor of a more simpler programming model with for-loops and visited tracker.

Changed files:
filename: packages/next/src/server/lib/router-utils/typegen.ts
patch: @@ -189,41 +189,60 @@ function formatRouteToRouteType(route: string) {
 }
 
 // Helper function to serialize route types (matches the plugin logic exactly)
-function serializeRouteTypes(routeTypes: string[]) {
+// Each entry is a [routeType, source] tuple.
+function serializeRouteTypes(routeTypes: [routeType: string, cause: string][]) {
   // route collection is not deterministic, this makes the output of the file deterministic
-  return routeTypes
-    .sort()
-    .map((route) => `\n    | \`${route}\``)
-    .join('')
+  routeTypes.sort(([a], [b]) => a.localeCompare(b))
+  let union = ''
+  for (let i = 0; i < routeTypes.length; i++) {
+    const [route, cause] = routeTypes[i]
+    union += `\n    | \`${route}\` // ${cause}`
+  }
+  return union
 }
 
 export function generateLinkTypesFile(
   routesManifest: RouteTypesManifest
 ): string {
-  // Generate serialized static and dynamic routes for the internal namespace
-  // Build a unified set of routes across app/pages/redirect/rewrite as well as
-  // app route handlers and Pages Router API routes.
-  const allRoutesSet = new Set<string>([
-    ...Object.keys(routesManifest.appRoutes),
-    ...Object.keys(routesManifest.pageRoutes),
-    ...Object.keys(routesManifest.redirectRoutes),
-    ...Object.keys(routesManifest.rewriteRoutes),
-    // Allow linking to App Route Handlers (e.g. `/logout/route.ts`)
-    ...Object.keys(routesManifest.appRouteHandlerRoutes),
-    // Allow linking to Pages Router API routes (e.g. `/api/*`)
-    ...Array.from(routesManifest.pageApiRoutes),
-  ])
-
-  const staticRouteTypes: string[] = []
-  const dynamicRouteTypes: string[] = []
-
-  // Process each route using the same logic as the plugin
-  for (const route of allRoutesSet) {
-    const { isDynamic, routeType } = formatRouteToRouteType(route)
+  const visited = new Set<string>()
+  const staticRouteTypes: [routeType: string, cause: string][] = []
+  const dynamicRouteTypes: [routeType: string, cause: string][] = []
+
+  for (const routeMap of [
+    routesManifest.appRoutes,
+    routesManifest.pageRoutes,
+    routesManifest.redirectRoutes,
+    routesManifest.rewriteRoutes,
+    routesManifest.appRouteHandlerRoutes,
+  ]) {
+    for (const route in routeMap) {
+      if (visited.has(route)) {
+        continue
+      }
+      visited.add(route)
+
+      const { isDynamic, routeType } = formatRouteToRouteType(route)
+      const cause = routeMap[route].path
+      if (isDynamic) {
+        dynamicRouteTypes.push([routeType, cause])
+      } else {
+        staticRouteTypes.push([routeType, cause])
+      }
+    }
+  }
+
+  // Pages Router API routes are stored as file paths
+  for (const filePath of routesManifest.pageApiRoutes) {
+    if (visited.has(filePath)) {
+      continue
+    }
+    visited.add(filePath)
+
+    const { isDynamic, routeType } = formatRouteToRouteType(filePath)
     if (isDynamic) {
-      dynamicRouteTypes.push(routeType)
+      dynamicRouteTypes.push([routeType, filePath])
     } else {
-      staticRouteTypes.push(routeType)
+      staticRouteTypes.push([routeType, filePath])
     }
   }