Repository: TanStack/router (ts)

Issue:
"/"  goes to an empty page, instead of index.tsx
### Which project does this relate to?

Router

### Describe the bug

using file/dir based routing

when using group routes, and having no index.ts directly inside src, "/" goes to an empty page instead of a 404 or the nearest index.tsx

### Your Example Website or App

https://github.com/FatahChan/tanstack-router-bugs

### Steps to Reproduce the Bug or Issue

1. pnpm install
2. pnpm dev
3. go to localhost:defaultport

renders empty page, check the router dev tools

### Expected behavior

As a user, I expected that the nearest  index.tsx page to the routes to be render.



### Screenshots or Videos

<img width="640" alt="Image" src="https://github.com/user-attachments/assets/c03ffac6-10bb-4d4c-9379-87443a47c941" />

<img width="1774" alt="Image" src="https://github.com/user-attachments/assets/7b4f80bb-6930-4039-80f6-bd89cacbbff7" />

### Platform

- OS: mac
- Browser: chrome
- Version: 132.0.6834.111


### Additional context

_No response_

Pull request description:
fix(router-generator): reset route-group Regex when calling `handleNode`
This was a weird one 🤔. 

Since the Regex we use for checking if a "route-node" is a possible "route-group" uses the global flag, it carries some state across runs. As such, it falsly returned `false` (instead of `true`) on some nodes that were in fact representing a "route-group".

Closes #3280 

Patch:
diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts
index c714e3f89..b0de40905 100644
--- a/packages/router-generator/src/generator.ts
+++ b/packages/router-generator/src/generator.ts
@@ -10,6 +10,7 @@ import {
   removeTrailingSlash,
   removeUnderscores,
   replaceBackslash,
+  resetRegex,
   routePathToVariable,
   trimPathLeft,
   writeIfDifferent,
@@ -158,6 +159,11 @@ export async function generator(config: Config, root: string) {
   await handleRootNode(rootRouteNode)
 
   const handleNode = async (node: RouteNode) => {
+    // Do not remove this as we need to set the lastIndex to 0 as it
+    // is necessary to reset the regex's index when using the global flag
+    // otherwise it might not match the next time it's used
+    resetRegex(routeGroupPatternRegex)
+
     let parentRoute = hasParentRoute(routeNodes, node, node.routePath)
 
     // if the parent route is a virtual parent route, we need to find the real parent route
diff --git a/packages/router-generator/src/utils.ts b/packages/router-generator/src/utils.ts
index 01051f8c2..1ff4f8383 100644
--- a/packages/router-generator/src/utils.ts
+++ b/packages/router-generator/src/utils.ts
@@ -150,3 +150,16 @@ export async function writeIfDifferent(
 
   return false
 }
+
+/**
+ * This function resets the regex index to 0 so that it can be reused
+ * without having to create a new regex object or worry about the last
+ * state when using the global flag.
+ *
+ * @param regex The regex object to reset
+ * @returns
+ */
+export function resetRegex(regex: RegExp) {
+  regex.lastIndex = 0
+  return
+}


Test patch:
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.snapshot.ts b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.snapshot.ts
new file mode 100644
index 000000000..9a8c43334
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routeTree.snapshot.ts
@@ -0,0 +1,369 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { createFileRoute } from '@tanstack/react-router'
+
+// Import Routes
+
+import { Route as rootRoute } from './routes/__root'
+import { Route as groupCLayoutCImport } from './routes/(group-c)/_layout-c'
+import { Route as groupBLayoutBImport } from './routes/(group-b)/_layout-b'
+import { Route as groupALayoutAImport } from './routes/(group-a)/_layout-a'
+import { Route as groupCLayoutCIndexImport } from './routes/(group-c)/_layout-c/index'
+import { Route as groupBLayoutBDashboardImport } from './routes/(group-b)/_layout-b/dashboard'
+import { Route as groupALayoutASignupImport } from './routes/(group-a)/_layout-a/signup'
+import { Route as groupALayoutALoginImport } from './routes/(group-a)/_layout-a/login'
+
+// Create Virtual Routes
+
+const groupCImport = createFileRoute('/(group-c)')()
+const groupBImport = createFileRoute('/(group-b)')()
+const groupAImport = createFileRoute('/(group-a)')()
+
+// Create/Update Routes
+
+const groupCRoute = groupCImport.update({
+  id: '/(group-c)',
+  getParentRoute: () => rootRoute,
+} as any)
+
+const groupBRoute = groupBImport.update({
+  id: '/(group-b)',
+  getParentRoute: () => rootRoute,
+} as any)
+
+const groupARoute = groupAImport.update({
+  id: '/(group-a)',
+  getParentRoute: () => rootRoute,
+} as any)
+
+const groupCLayoutCRoute = groupCLayoutCImport.update({
+  id: '/_layout-c',
+  getParentRoute: () => groupCRoute,
+} as any)
+
+const groupBLayoutBRoute = groupBLayoutBImport.update({
+  id: '/_layout-b',
+  getParentRoute: () => groupBRoute,
+} as any)
+
+const groupALayoutARoute = groupALayoutAImport.update({
+  id: '/_layout-a',
+  getParentRoute: () => groupARoute,
+} as any)
+
+const groupCLayoutCIndexRoute = groupCLayoutCIndexImport.update({
+  id: '/',
+  path: '/',
+  getParentRoute: () => groupCLayoutCRoute,
+} as any)
+
+const groupBLayoutBDashboardRoute = groupBLayoutBDashboardImport.update({
+  id: '/dashboard',
+  path: '/dashboard',
+  getParentRoute: () => groupBLayoutBRoute,
+} as any)
+
+const groupALayoutASignupRoute = groupALayoutASignupImport.update({
+  id: '/signup',
+  path: '/signup',
+  getParentRoute: () => groupALayoutARoute,
+} as any)
+
+const groupALayoutALoginRoute = groupALayoutALoginImport.update({
+  id: '/login',
+  path: '/login',
+  getParentRoute: () => groupALayoutARoute,
+} as any)
+
+// Populate the FileRoutesByPath interface
+
+declare module '@tanstack/react-router' {
+  interface FileRoutesByPath {
+    '/(group-a)': {
+      id: '/(group-a)'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof groupAImport
+      parentRoute: typeof rootRoute
+    }
+    '/(group-a)/_layout-a': {
+      id: '/(group-a)/_layout-a'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof groupALayoutAImport
+      parentRoute: typeof groupARoute
+    }
+    '/(group-b)': {
+      id: '/(group-b)'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof groupBImport
+      parentRoute: typeof rootRoute
+    }
+    '/(group-b)/_layout-b': {
+      id: '/(group-b)/_layout-b'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof groupBLayoutBImport
+      parentRoute: typeof groupBRoute
+    }
+    '/(group-c)': {
+      id: '/(group-c)'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof groupCImport
+      parentRoute: typeof rootRoute
+    }
+    '/(group-c)/_layout-c': {
+      id: '/(group-c)/_layout-c'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof groupCLayoutCImport
+      parentRoute: typeof groupCRoute
+    }
+    '/(group-a)/_layout-a/login': {
+      id: '/(group-a)/_layout-a/login'
+      path: '/login'
+      fullPath: '/login'
+      preLoaderRoute: typeof groupALayoutALoginImport
+      parentRoute: typeof groupALayoutAImport
+    }
+    '/(group-a)/_layout-a/signup': {
+      id: '/(group-a)/_layout-a/signup'
+      path: '/signup'
+      fullPath: '/signup'
+      preLoaderRoute: typeof groupALayoutASignupImport
+      parentRoute: typeof groupALayoutAImport
+    }
+    '/(group-b)/_layout-b/dashboard': {
+      id: '/(group-b)/_layout-b/dashboard'
+      path: '/dashboard'
+      fullPath: '/dashboard'
+      preLoaderRoute: typeof groupBLayoutBDashboardImport
+      parentRoute: typeof groupBLayoutBImport
+    }
+    '/(group-c)/_layout-c/': {
+      id: '/(group-c)/_layout-c/'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof groupCLayoutCIndexImport
+      parentRoute: typeof groupCLayoutCImport
+    }
+  }
+}
+
+// Create and export the route tree
+
+interface groupALayoutARouteChildren {
+  groupALayoutALoginRoute: typeof groupALayoutALoginRoute
+  groupALayoutASignupRoute: typeof groupALayoutASignupRoute
+}
+
+const groupALayoutARouteChildren: groupALayoutARouteChildren = {
+  groupALayoutALoginRoute: groupALayoutALoginRoute,
+  groupALayoutASignupRoute: groupALayoutASignupRoute,
+}
+
+const groupALayoutARouteWithChildren = groupALayoutARoute._addFileChildren(
+  groupALayoutARouteChildren,
+)
+
+interface groupARouteChildren {
+  groupALayoutARoute: typeof groupALayoutARouteWithChildren
+}
+
+const groupARouteChildren: groupARouteChildren = {
+  groupALayoutARoute: groupALayoutARouteWithChildren,
+}
+
+const groupARouteWithChildren =
+  groupARoute._addFileChildren(groupARouteChildren)
+
+interface groupBLayoutBRouteChildren {
+  groupBLayoutBDashboardRoute: typeof groupBLayoutBDashboardRoute
+}
+
+const groupBLayoutBRouteChildren: groupBLayoutBRouteChildren = {
+  groupBLayoutBDashboardRoute: groupBLayoutBDashboardRoute,
+}
+
+const groupBLayoutBRouteWithChildren = groupBLayoutBRoute._addFileChildren(
+  groupBLayoutBRouteChildren,
+)
+
+interface groupBRouteChildren {
+  groupBLayoutBRoute: typeof groupBLayoutBRouteWithChildren
+}
+
+const groupBRouteChildren: groupBRouteChildren = {
+  groupBLayoutBRoute: groupBLayoutBRouteWithChildren,
+}
+
+const groupBRouteWithChildren =
+  groupBRoute._addFileChildren(groupBRouteChildren)
+
+interface groupCLayoutCRouteChildren {
+  groupCLayoutCIndexRoute: typeof groupCLayoutCIndexRoute
+}
+
+const groupCLayoutCRouteChildren: groupCLayoutCRouteChildren = {
+  groupCLayoutCIndexRoute: groupCLayoutCIndexRoute,
+}
+
+const groupCLayoutCRouteWithChildren = groupCLayoutCRoute._addFileChildren(
+  groupCLayoutCRouteChildren,
+)
+
+interface groupCRouteChildren {
+  groupCLayoutCRoute: typeof groupCLayoutCRouteWithChildren
+}
+
+const groupCRouteChildren: groupCRouteChildren = {
+  groupCLayoutCRoute: groupCLayoutCRouteWithChildren,
+}
+
+const groupCRouteWithChildren =
+  groupCRoute._addFileChildren(groupCRouteChildren)
+
+export interface FileRoutesByFullPath {
+  '/': typeof groupCLayoutCIndexRoute
+  '/login': typeof groupALayoutALoginRoute
+  '/signup': typeof groupALayoutASignupRoute
+  '/dashboard': typeof groupBLayoutBDashboardRoute
+}
+
+export interface FileRoutesByTo {
+  '/': typeof groupCLayoutCIndexRoute
+  '/login': typeof groupALayoutALoginRoute
+  '/signup': typeof groupALayoutASignupRoute
+  '/dashboard': typeof groupBLayoutBDashboardRoute
+}
+
+export interface FileRoutesById {
+  __root__: typeof rootRoute
+  '/(group-a)': typeof groupARouteWithChildren
+  '/(group-a)/_layout-a': typeof groupALayoutARouteWithChildren
+  '/(group-b)': typeof groupBRouteWithChildren
+  '/(group-b)/_layout-b': typeof groupBLayoutBRouteWithChildren
+  '/(group-c)': typeof groupCRouteWithChildren
+  '/(group-c)/_layout-c': typeof groupCLayoutCRouteWithChildren
+  '/(group-a)/_layout-a/login': typeof groupALayoutALoginRoute
+  '/(group-a)/_layout-a/signup': typeof groupALayoutASignupRoute
+  '/(group-b)/_layout-b/dashboard': typeof groupBLayoutBDashboardRoute
+  '/(group-c)/_layout-c/': typeof groupCLayoutCIndexRoute
+}
+
+export interface FileRouteTypes {
+  fileRoutesByFullPath: FileRoutesByFullPath
+  fullPaths: '/' | '/login' | '/signup' | '/dashboard'
+  fileRoutesByTo: FileRoutesByTo
+  to: '/' | '/login' | '/signup' | '/dashboard'
+  id:
+    | '__root__'
+    | '/(group-a)'
+    | '/(group-a)/_layout-a'
+    | '/(group-b)'
+    | '/(group-b)/_layout-b'
+    | '/(group-c)'
+    | '/(group-c)/_layout-c'
+    | '/(group-a)/_layout-a/login'
+    | '/(group-a)/_layout-a/signup'
+    | '/(group-b)/_layout-b/dashboard'
+    | '/(group-c)/_layout-c/'
+  fileRoutesById: FileRoutesById
+}
+
+export interface RootRouteChildren {
+  groupARoute: typeof groupARouteWithChildren
+  groupBRoute: typeof groupBRouteWithChildren
+  groupCRoute: typeof groupCRouteWithChildren
+}
+
+const rootRouteChildren: RootRouteChildren = {
+  groupARoute: groupARouteWithChildren,
+  groupBRoute: groupBRouteWithChildren,
+  groupCRoute: groupCRouteWithChildren,
+}
+
+export const routeTree = rootRoute
+  ._addFileChildren(rootRouteChildren)
+  ._addFileTypes<FileRouteTypes>()
+
+/* ROUTE_MANIFEST_START
+{
+  "routes": {
+    "__root__": {
+      "filePath": "__root.tsx",
+      "children": [
+        "/(group-a)",
+        "/(group-b)",
+        "/(group-c)"
+      ]
+    },
+    "/(group-a)": {
+      "filePath": "(group-a)",
+      "children": [
+        "/(group-a)/_layout-a"
+      ]
+    },
+    "/(group-a)/_layout-a": {
+      "filePath": "(group-a)/_layout-a.tsx",
+      "parent": "/(group-a)",
+      "children": [
+        "/(group-a)/_layout-a/login",
+        "/(group-a)/_layout-a/signup"
+      ]
+    },
+    "/(group-b)": {
+      "filePath": "(group-b)",
+      "children": [
+        "/(group-b)/_layout-b"
+      ]
+    },
+    "/(group-b)/_layout-b": {
+      "filePath": "(group-b)/_layout-b.tsx",
+      "parent": "/(group-b)",
+      "children": [
+        "/(group-b)/_layout-b/dashboard"
+      ]
+    },
+    "/(group-c)": {
+      "filePath": "(group-c)",
+      "children": [
+        "/(group-c)/_layout-c"
+      ]
+    },
+    "/(group-c)/_layout-c": {
+      "filePath": "(group-c)/_layout-c.tsx",
+      "parent": "/(group-c)",
+      "children": [
+        "/(group-c)/_layout-c/"
+      ]
+    },
+    "/(group-a)/_layout-a/login": {
+      "filePath": "(group-a)/_layout-a/login.tsx",
+      "parent": "/(group-a)/_layout-a"
+    },
+    "/(group-a)/_layout-a/signup": {
+      "filePath": "(group-a)/_layout-a/signup.tsx",
+      "parent": "/(group-a)/_layout-a"
+    },
+    "/(group-b)/_layout-b/dashboard": {
+      "filePath": "(group-b)/_layout-b/dashboard.tsx",
+      "parent": "/(group-b)/_layout-b"
+    },
+    "/(group-c)/_layout-c/": {
+      "filePath": "(group-c)/_layout-c/index.tsx",
+      "parent": "/(group-c)/_layout-c"
+    }
+  }
+}
+ROUTE_MANIFEST_END */
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a.tsx b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a.tsx
new file mode 100644
index 000000000..e41597bd1
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/(group-a)/_layout-a')({
+  component: RouteComponent,
+})
+
+function RouteComponent() {
+  return <div>Hello "/(group-a)/_layout-a"!</div>
+}
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a/login.tsx b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a/login.tsx
new file mode 100644
index 000000000..100a98b81
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a/login.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/(group-a)/_layout-a/login')({
+  component: RouteComponent,
+})
+
+function RouteComponent() {
+  return <div>Hello "/(group-a)/_layout-a/login"!</div>
+}
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a/signup.tsx b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a/signup.tsx
new file mode 100644
index 000000000..2214d268e
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-a)/_layout-a/signup.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/(group-a)/_layout-a/signup')({
+  component: RouteComponent,
+})
+
+function RouteComponent() {
+  return <div>Hello "/(group-a)/_layout-a/signup"!</div>
+}
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-b)/_layout-b.tsx b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-b)/_layout-b.tsx
new file mode 100644
index 000000000..9dc904361
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-b)/_layout-b.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/(group-b)/_layout-b')({
+  component: RouteComponent,
+})
+
+function RouteComponent() {
+  return <div>Hello "/(group-b)/_layout-b"!</div>
+}
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-b)/_layout-b/dashboard.tsx b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-b)/_layout-b/dashboard.tsx
new file mode 100644
index 000000000..c265a0015
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-b)/_layout-b/dashboard.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/(group-b)/_layout-b/dashboard')({
+  component: RouteComponent,
+})
+
+function RouteComponent() {
+  return <div>Hello "/(group-b)/_layout/dashboard"!</div>
+}
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-c)/_layout-c.tsx b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-c)/_layout-c.tsx
new file mode 100644
index 000000000..37483d2b5
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-c)/_layout-c.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/(group-c)/_layout-c')({
+  component: RouteComponent,
+})
+
+function RouteComponent() {
+  return <div>Hello "/(group-c)/_layout-c"!</div>
+}
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-c)/_layout-c/index.tsx b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-c)/_layout-c/index.tsx
new file mode 100644
index 000000000..31a16f051
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/(group-c)/_layout-c/index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/(group-c)/_layout-c/')({
+  component: RouteComponent,
+})
+
+function RouteComponent() {
+  return <div>Hello "/(group-c)/_layout-c/"!</div>
+}
diff --git a/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/__root.tsx b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/__root.tsx
new file mode 100644
index 000000000..3797d7bf8
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-route-groups-with-layouts-before-physical/routes/__root.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react'
+import { Outlet, createRootRoute } from '@tanstack/react-router'
+
+export const Route = createRootRoute({
+  component: RootComponent,
+})
+
+function RootComponent() {
+  return (
+    <React.Fragment>
+      <div>Hello "__root"!</div>
+      <Outlet />
+    </React.Fragment>
+  )
+}