diff --git a/e2e/react-router/basic-file-based-custom-config/.gitignore b/e2e/react-router/basic-file-based-custom-config/.gitignore
new file mode 100644
index 0000000000..4d2da67b50
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/.gitignore
@@ -0,0 +1,11 @@
+node_modules
+.DS_Store
+dist
+dist-hash
+dist-ssr
+*.local
+
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/e2e/react-router/basic-file-based-custom-config/package.json b/e2e/react-router/basic-file-based-custom-config/package.json
new file mode 100644
index 0000000000..cd4091e4d4
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "tanstack-router-e2e-react-basic-file-based-custom-config",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port 3000",
+ "dev:e2e": "vite",
+ "build": "vite build && tsc --noEmit",
+ "serve": "vite preview",
+ "start": "vite",
+ "test:e2e": "playwright test --project=chromium"
+ },
+ "dependencies": {
+ "@tanstack/react-router": "workspace:^",
+ "@tanstack/react-router-devtools": "workspace:^",
+ "@tanstack/router-plugin": "workspace:^",
+ "@tanstack/zod-adapter": "workspace:^",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "redaxios": "^0.5.1",
+ "postcss": "^8.5.1",
+ "autoprefixer": "^10.4.20",
+ "tailwindcss": "^3.4.17",
+ "zod": "^3.24.2"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.50.1",
+ "@tanstack/router-e2e-utils": "workspace:^",
+ "@types/react": "^19.0.8",
+ "@types/react-dom": "^19.0.3",
+ "@vitejs/plugin-react": "^4.3.4",
+ "combinate": "^1.1.11",
+ "vite": "^6.1.0"
+ }
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/playwright.config.ts b/e2e/react-router/basic-file-based-custom-config/playwright.config.ts
new file mode 100644
index 0000000000..2eeb79844f
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/playwright.config.ts
@@ -0,0 +1,34 @@
+import { defineConfig, devices } from '@playwright/test'
+import { derivePort } from '@tanstack/router-e2e-utils'
+import packageJson from './package.json' with { type: 'json' }
+
+const PORT = derivePort(packageJson.name)
+const baseURL = `http://localhost:${PORT}`
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './tests',
+ workers: 1,
+
+ reporter: [['line']],
+
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL,
+ },
+
+ webServer: {
+ command: `VITE_SERVER_PORT=${PORT} pnpm build && VITE_SERVER_PORT=${PORT} pnpm serve --port ${PORT}`,
+ url: baseURL,
+ reuseExistingServer: !process.env.CI,
+ stdout: 'pipe',
+ },
+
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+})
diff --git a/e2e/react-router/basic-file-based-custom-config/postcss.config.mjs b/e2e/react-router/basic-file-based-custom-config/postcss.config.mjs
new file mode 100644
index 0000000000..2e7af2b7f1
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/postcss.config.mjs
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/src/index.html b/e2e/react-router/basic-file-based-custom-config/src/index.html
new file mode 100644
index 0000000000..d719363d5b
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/react-router/basic-file-based-custom-config/src/main.tsx b/e2e/react-router/basic-file-based-custom-config/src/main.tsx
new file mode 100644
index 0000000000..3dc73ddd51
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/main.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import { RouterProvider, createRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+import './styles.css'
+
+// Set up a Router instance
+const router = createRouter({
+ routeTree,
+ defaultPreload: 'intent',
+ defaultStaleTime: 5000,
+ scrollRestoration: true,
+})
+
+// Register things for typesafety
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: typeof router
+ }
+}
+
+const rootElement = document.getElementById('app')!
+
+if (!rootElement.innerHTML) {
+ const root = ReactDOM.createRoot(rootElement)
+ root.render()
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/src/posts.tsx b/e2e/react-router/basic-file-based-custom-config/src/posts.tsx
new file mode 100644
index 0000000000..3ccf1ff421
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/posts.tsx
@@ -0,0 +1,32 @@
+import { notFound } from '@tanstack/react-router'
+import axios from 'redaxios'
+
+export type PostType = {
+ id: string
+ title: string
+ body: string
+}
+
+export const fetchPost = async (postId: string) => {
+ console.info(`Fetching post with id ${postId}...`)
+ await new Promise((r) => setTimeout(r, 500))
+ const post = await axios
+ .get(`https://jsonplaceholder.typicode.com/posts/${postId}`)
+ .then((r) => r.data)
+ .catch((err) => {
+ if (err.status === 404) {
+ throw notFound()
+ }
+ throw err
+ })
+
+ return post
+}
+
+export const fetchPosts = async () => {
+ console.info('Fetching posts...')
+ await new Promise((r) => setTimeout(r, 500))
+ return axios
+ .get>('https://jsonplaceholder.typicode.com/posts')
+ .then((r) => r.data.slice(0, 10))
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/src/routeTree.gen.ts b/e2e/react-router/basic-file-based-custom-config/src/routeTree.gen.ts
new file mode 100644
index 0000000000..7b3c3ada94
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/routeTree.gen.ts
@@ -0,0 +1,168 @@
+/* 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 Routes
+
+import { Route as rootRoute } from './routes/__root'
+import { Route as PostsImport } from './routes/posts'
+import { Route as IndexImport } from './routes/index'
+import { Route as PostsIndexImport } from './routes/posts.index'
+import { Route as PostsPostIdImport } from './routes/posts.$postId'
+
+// Create/Update Routes
+
+const PostsRoute = PostsImport.update({
+ id: '/posts',
+ path: '/posts',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const IndexRoute = IndexImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const PostsIndexRoute = PostsIndexImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => PostsRoute,
+} as any)
+
+const PostsPostIdRoute = PostsPostIdImport.update({
+ id: '/$postId',
+ path: '/$postId',
+ getParentRoute: () => PostsRoute,
+} as any)
+
+// Populate the FileRoutesByPath interface
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexImport
+ parentRoute: typeof rootRoute
+ }
+ '/posts': {
+ id: '/posts'
+ path: '/posts'
+ fullPath: '/posts'
+ preLoaderRoute: typeof PostsImport
+ parentRoute: typeof rootRoute
+ }
+ '/posts/$postId': {
+ id: '/posts/$postId'
+ path: '/$postId'
+ fullPath: '/posts/$postId'
+ preLoaderRoute: typeof PostsPostIdImport
+ parentRoute: typeof PostsImport
+ }
+ '/posts/': {
+ id: '/posts/'
+ path: '/'
+ fullPath: '/posts/'
+ preLoaderRoute: typeof PostsIndexImport
+ parentRoute: typeof PostsImport
+ }
+ }
+}
+
+// Create and export the route tree
+
+interface PostsRouteChildren {
+ PostsPostIdRoute: typeof PostsPostIdRoute
+ PostsIndexRoute: typeof PostsIndexRoute
+}
+
+const PostsRouteChildren: PostsRouteChildren = {
+ PostsPostIdRoute: PostsPostIdRoute,
+ PostsIndexRoute: PostsIndexRoute,
+}
+
+const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/posts': typeof PostsRouteWithChildren
+ '/posts/$postId': typeof PostsPostIdRoute
+ '/posts/': typeof PostsIndexRoute
+}
+
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/posts/$postId': typeof PostsPostIdRoute
+ '/posts': typeof PostsIndexRoute
+}
+
+export interface FileRoutesById {
+ __root__: typeof rootRoute
+ '/': typeof IndexRoute
+ '/posts': typeof PostsRouteWithChildren
+ '/posts/$postId': typeof PostsPostIdRoute
+ '/posts/': typeof PostsIndexRoute
+}
+
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths: '/' | '/posts' | '/posts/$postId' | '/posts/'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/posts/$postId' | '/posts'
+ id: '__root__' | '/' | '/posts' | '/posts/$postId' | '/posts/'
+ fileRoutesById: FileRoutesById
+}
+
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ PostsRoute: typeof PostsRouteWithChildren
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ PostsRoute: PostsRouteWithChildren,
+}
+
+export const routeTree = rootRoute
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+/* ROUTE_MANIFEST_START
+{
+ "routes": {
+ "__root__": {
+ "filePath": "__root.tsx",
+ "children": [
+ "/",
+ "/posts"
+ ]
+ },
+ "/": {
+ "filePath": "index.tsx"
+ },
+ "/posts": {
+ "filePath": "posts.tsx",
+ "children": [
+ "/posts/$postId",
+ "/posts/"
+ ]
+ },
+ "/posts/$postId": {
+ "filePath": "posts.$postId.tsx",
+ "parent": "/posts"
+ },
+ "/posts/": {
+ "filePath": "posts.index.tsx",
+ "parent": "/posts"
+ }
+ }
+}
+ROUTE_MANIFEST_END */
diff --git a/e2e/react-router/basic-file-based-custom-config/src/routes/__root.tsx b/e2e/react-router/basic-file-based-custom-config/src/routes/__root.tsx
new file mode 100644
index 0000000000..a6814aba08
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/routes/__root.tsx
@@ -0,0 +1,75 @@
+import {
+ HeadContent,
+ Link,
+ Outlet,
+ createRootRoute,
+ useCanGoBack,
+ useRouter,
+ useRouterState,
+} from '@tanstack/react-router'
+import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
+
+export const Route = createRootRoute({
+ component: RootComponent,
+ notFoundComponent: () => {
+ return (
+
+
This is the notFoundComponent configured on root route
+
Start Over
+
+ )
+ },
+})
+
+function RootComponent() {
+ const router = useRouter()
+ const canGoBack = useCanGoBack()
+ // test useRouterState doesn't crash client side navigation
+ const _state = useRouterState()
+
+ return (
+ <>
+
+
+ {' '}
+
+ Home
+ {' '}
+
+ Posts
+ {' '}
+
+ This Route Does Not Exist
+
+
+
+
+ {/* Start rendering router matches */}
+
+ >
+ )
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/src/routes/index.tsx b/e2e/react-router/basic-file-based-custom-config/src/routes/index.tsx
new file mode 100644
index 0000000000..eac82a9174
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/routes/index.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react'
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: Home,
+})
+
+function Home() {
+ return (
+
+
Welcome Home!
+
+ )
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/src/routes/posts.$postId.tsx b/e2e/react-router/basic-file-based-custom-config/src/routes/posts.$postId.tsx
new file mode 100644
index 0000000000..df51873ddf
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/routes/posts.$postId.tsx
@@ -0,0 +1,30 @@
+import * as React from 'react'
+import { ErrorComponent, createFileRoute } from '@tanstack/react-router'
+import { fetchPost } from '../posts'
+import type { ErrorComponentProps } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/posts/$postId')({
+ loader: async ({ params: { postId } }) => fetchPost(postId),
+ errorComponent: PostErrorComponent,
+ notFoundComponent: () => {
+ return Post not found
+ },
+ component: PostComponent,
+})
+
+export function PostErrorComponent({ error }: ErrorComponentProps) {
+ return
+}
+
+function PostComponent() {
+ const post = Route.useLoaderData()
+
+ return (
+
+
+ {post.title}
+
+
{post.body}
+
+ )
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/src/routes/posts.index.tsx b/e2e/react-router/basic-file-based-custom-config/src/routes/posts.index.tsx
new file mode 100644
index 0000000000..ab89fb5a62
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/routes/posts.index.tsx
@@ -0,0 +1,10 @@
+import * as React from 'react'
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/posts/')({
+ component: PostsIndexComponent,
+})
+
+function PostsIndexComponent() {
+ return Select a post.
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/src/routes/posts.tsx b/e2e/react-router/basic-file-based-custom-config/src/routes/posts.tsx
new file mode 100644
index 0000000000..9eabdd5b33
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/routes/posts.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react'
+import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
+import { fetchPosts } from '../posts'
+
+export const Route = createFileRoute('/posts')({
+ head: () => ({
+ meta: [
+ {
+ title: 'Posts page',
+ },
+ ],
+ }),
+ loader: fetchPosts,
+ component: PostsComponent,
+})
+
+function PostsComponent() {
+ const posts = Route.useLoaderData()
+
+ return (
+
+ )
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/src/styles.css b/e2e/react-router/basic-file-based-custom-config/src/styles.css
new file mode 100644
index 0000000000..0b8e317099
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/src/styles.css
@@ -0,0 +1,13 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+html {
+ color-scheme: light dark;
+}
+* {
+ @apply border-gray-200 dark:border-gray-800;
+}
+body {
+ @apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/tailwind.config.mjs b/e2e/react-router/basic-file-based-custom-config/tailwind.config.mjs
new file mode 100644
index 0000000000..4986094b9d
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/tailwind.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./src/**/*.{js,jsx,ts,tsx}', './index.html'],
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/tests/app.spec.ts b/e2e/react-router/basic-file-based-custom-config/tests/app.spec.ts
new file mode 100644
index 0000000000..5b1fc3854e
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/tests/app.spec.ts
@@ -0,0 +1,20 @@
+import { expect, test } from '@playwright/test'
+
+test.beforeEach(async ({ page }) => {
+ await page.goto('/')
+})
+
+test('Navigating to a post page', async ({ page }) => {
+ await page.getByRole('link', { name: 'Posts' }).click()
+ await page.getByRole('link', { name: 'sunt aut facere repe' }).click()
+ await expect(page.getByRole('heading')).toContainText('sunt aut facere')
+})
+
+test('Navigating to a not-found route', async ({ page }) => {
+ await page.getByRole('link', { name: 'This Route Does Not Exist' }).click()
+ await expect(page.getByRole('paragraph')).toContainText(
+ 'This is the notFoundComponent configured on root route',
+ )
+ await page.getByRole('link', { name: 'Start Over' }).click()
+ await expect(page.getByRole('heading')).toContainText('Welcome Home!')
+})
diff --git a/e2e/react-router/basic-file-based-custom-config/tsconfig.json b/e2e/react-router/basic-file-based-custom-config/tsconfig.json
new file mode 100644
index 0000000000..5db3b20eba
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "target": "ESNext",
+ "moduleResolution": "Bundler",
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "resolveJsonModule": true,
+ "allowJs": true
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/e2e/react-router/basic-file-based-custom-config/vite.config.js b/e2e/react-router/basic-file-based-custom-config/vite.config.js
new file mode 100644
index 0000000000..cd88ff6a64
--- /dev/null
+++ b/e2e/react-router/basic-file-based-custom-config/vite.config.js
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ root: "./src",
+ plugins: [TanStackRouterVite({ target: 'react' }), react()],
+})
diff --git a/e2e/react-router/basic-file-based/src/index.html b/e2e/react-router/basic-file-based/src/index.html
new file mode 100644
index 0000000000..d719363d5b
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/react/kitchen-sink-file-based/index.html b/examples/react/kitchen-sink-file-based/src/index.html
similarity index 80%
rename from examples/react/kitchen-sink-file-based/index.html
rename to examples/react/kitchen-sink-file-based/src/index.html
index 9b6335c0ac..645fadca47 100644
--- a/examples/react/kitchen-sink-file-based/index.html
+++ b/examples/react/kitchen-sink-file-based/src/index.html
@@ -7,6 +7,6 @@
-
+