diff --git a/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.$.tsx b/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.$.tsx
deleted file mode 100644
index 7551b6e5aa..0000000000
--- a/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.$.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { ErrorComponent, createFileRoute } from '@tanstack/react-router'
-import type { ErrorComponentProps } from '@tanstack/react-router'
-import { NotFound } from '~/components/NotFound'
-import { fetchPost } from '~/utils/posts'
-
-export const Route = createFileRoute(
- '/nested-docs/$project/$version/docs/framework/$framework/$',
-)({
- loader: ({ params: { _splat } }) => {
- console.debug('👀 params._splat', _splat)
- return fetchPost(_splat!)
- },
- errorComponent: PostErrorComponent,
- component: Page,
- notFoundComponent: () => {
- return Post not found
- },
-})
-
-function PostErrorComponent({ error }: ErrorComponentProps) {
- return
-}
-
-function Page() {
- const post = Route.useLoaderData()
-
- return (
-
-
- Heading for ID = {post.id}
-
-
{post.body}
-
- )
-}
diff --git a/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.index.tsx b/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.index.tsx
deleted file mode 100644
index e1cdec0d96..0000000000
--- a/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.index.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { createFileRoute } from '@tanstack/react-router'
-
-export const Route = createFileRoute(
- '/nested-docs/$project/$version/docs/framework/$framework/',
-)({
- component: () => Selected a post by it's ID.
,
-})
diff --git a/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.tsx b/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.tsx
deleted file mode 100644
index e2aac7f9ce..0000000000
--- a/e2e/start/basic/app/routes/nested-docs.$project.$version.docs.framework.$framework.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
-import { fetchPosts } from '~/utils/posts'
-
-export const Route = createFileRoute(
- '/nested-docs/$project/$version/docs/framework/$framework',
-)({
- loader: () => fetchPosts(),
- component: Page,
-})
-
-function Page() {
- const posts = Route.useLoaderData()
- const params = Route.useParams()
-
- return (
- <>
-
-
Select project
-
- -
-
- Router
-
-
- -
-
- Query
-
-
- -
-
- Table
-
-
-
-
-
-
Select version
-
- -
-
- Latest
-
-
- -
-
- V2
-
-
- -
-
- V1
-
-
-
-
-
-
Select framework
-
- -
-
- React
-
-
- -
-
- SolidJS
-
-
- -
-
- Vue
-
-
-
-
-
- Selected:
-
- /nested-docs/{params.project}/{params.version}/docs/framework/
- {params.framework}
-
-
-
-
- {[...posts, { id: 'i-do-not-exist', title: 'Non-existent Post' }].map(
- (post) => {
- return (
- -
-
- {`Post ID = ${post.id}`}
-
-
- )
- },
- )}
-
-
-
-
- >
- )
-}
diff --git a/e2e/start/basic/app/routes/nested-docs.$project.$version.index.tsx b/e2e/start/basic/app/routes/nested-docs.$project.$version.index.tsx
deleted file mode 100644
index 4a19e47725..0000000000
--- a/e2e/start/basic/app/routes/nested-docs.$project.$version.index.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { createFileRoute, redirect } from '@tanstack/react-router'
-
-export const Route = createFileRoute('/nested-docs/$project/$version/')({
- loader: ({ params }) => {
- throw redirect({
- from: '/nested-docs/$project/$version',
- to: '/nested-docs/$project/$version/docs/framework/$framework',
- params: {
- project: params.project,
- version: params.version,
- framework: 'react',
- },
- })
- },
- component: () => Hello /nested-docs/$project/$version/!
,
-})
diff --git a/e2e/start/basic/app/routes/nested-docs.$project.index.tsx b/e2e/start/basic/app/routes/nested-docs.$project.index.tsx
deleted file mode 100644
index b48b584457..0000000000
--- a/e2e/start/basic/app/routes/nested-docs.$project.index.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createFileRoute, redirect } from '@tanstack/react-router'
-
-export const Route = createFileRoute('/nested-docs/$project/')({
- loader: ({ params }) => {
- throw redirect({
- to: '/nested-docs/$project/$version/docs/framework/$framework',
- params: {
- project: params.project,
- version: 'latest',
- framework: 'react',
- },
- })
- },
- component: () => Hello /nested-docs/$project/!
,
-})
diff --git a/e2e/start/basic/app/routes/nested-docs.index.tsx b/e2e/start/basic/app/routes/nested-docs.index.tsx
deleted file mode 100644
index 3bd21cbc08..0000000000
--- a/e2e/start/basic/app/routes/nested-docs.index.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { createFileRoute, redirect } from '@tanstack/react-router'
-
-export const Route = createFileRoute('/nested-docs/')({
- loader: () => {
- throw redirect({
- to: '/nested-docs/$project/$version/docs/framework/$framework',
- params: { project: 'router', version: 'latest', framework: 'react' },
- })
- },
- component: () => Hello /nested-docs/!
,
-})
diff --git a/e2e/start/basic/app/routes/nested-docs.tsx b/e2e/start/basic/app/routes/nested-docs.tsx
deleted file mode 100644
index 5ae416c552..0000000000
--- a/e2e/start/basic/app/routes/nested-docs.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Outlet, createFileRoute } from '@tanstack/react-router'
-
-export const Route = createFileRoute('/nested-docs')({
- component: () => (
-
-
-
- ),
-})
diff --git a/e2e/start/basic/tests/docs-layout.spec.ts b/e2e/start/basic/tests/docs-layout.spec.ts
deleted file mode 100644
index c6f829447a..0000000000
--- a/e2e/start/basic/tests/docs-layout.spec.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { expect, test } from '@playwright/test'
-
-const routeTestId = 'selected-route-label'
-
-test('index redirect resolves', async ({ page }) => {
- await page.goto('/nested-docs')
-
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/router/latest/docs/framework/react',
- )
-})
-
-test('can change the project', async ({ page }) => {
- await page.goto('/nested-docs')
- await page.getByRole('link', { name: 'Query' }).click()
-
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/query/latest/docs/framework/react',
- )
-})
-
-test('can change the version', async ({ page }) => {
- await page.goto('/nested-docs')
- await page.getByRole('link', { name: 'V1' }).click()
-
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/router/v1/docs/framework/react',
- )
-})
-
-test('can change the framework', async ({ page }) => {
- await page.goto('/nested-docs')
- await page.getByRole('link', { name: 'SolidJS' }).click()
-
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/router/latest/docs/framework/solidjs',
- )
-})
-
-test('navigate to post 1', async ({ page }) => {
- await page.goto('/nested-docs')
- await page.getByRole('link', { name: 'Post ID = 1', exact: true }).click()
-
- await expect(page.getByRole('heading')).toContainText('Heading for ID = 1')
-})
-
-test('change project from post 1', async ({ page }) => {
- await page.goto('/nested-docs/router/latest/docs/framework/react/1')
- await expect(page.getByRole('heading')).toContainText('Heading for ID = 1')
- await page.getByRole('link', { name: 'Query' }).click()
-
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/query/latest/docs/framework/react',
- )
-})
-
-test('change version from post 1', async ({ page }) => {
- await page.goto('/nested-docs/router/latest/docs/framework/react/1')
- await expect(page.getByRole('heading')).toContainText('Heading for ID = 1')
- await page.getByRole('link', { name: 'V2' }).click()
-
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/router/v2/docs/framework/react',
- )
-})
-
-test('change framework from post 1', async ({ page }) => {
- await page.goto('/nested-docs/router/latest/docs/framework/react/1')
- await expect(page.getByRole('heading')).toContainText('Heading for ID = 1')
- await page.getByRole('link', { name: 'SolidJS' }).click()
-
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/router/latest/docs/framework/solidjs',
- )
-})
-
-test('can change project,version,framework from root', async ({ page }) => {
- await page.goto('/nested-docs')
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/router/latest/docs/framework/react',
- )
-
- await page.getByRole('link', { name: 'Query' }).click()
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/query/latest/docs/framework/react',
- )
-
- await page.getByRole('link', { name: 'V2' }).click()
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/query/v2/docs/framework/react',
- )
-
- await page.getByRole('link', { name: 'SolidJS' }).click()
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/query/v2/docs/framework/solidjs',
- )
-})
-
-test('can change project,version,framework from post 1', async ({ page }) => {
- await page.goto('/nested-docs/router/latest/docs/framework/react/1')
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/router/latest/docs/framework/react',
- )
-
- await page.getByRole('link', { name: 'Query' }).click()
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/query/latest/docs/framework/react',
- )
-
- await page.getByRole('link', { name: 'V2' }).click()
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/query/v2/docs/framework/react',
- )
-
- await page.getByRole('link', { name: 'SolidJS' }).click()
- await expect(page.getByTestId(routeTestId)).toContainText(
- '/nested-docs/query/v2/docs/framework/solidjs',
- )
-})
diff --git a/e2e/start/website/.gitignore b/e2e/start/website/.gitignore
new file mode 100644
index 0000000000..be342025da
--- /dev/null
+++ b/e2e/start/website/.gitignore
@@ -0,0 +1,22 @@
+node_modules
+package-lock.json
+yarn.lock
+
+.DS_Store
+.cache
+.env
+.vercel
+.output
+.vinxi
+
+/build/
+/api/
+/server/build
+/public/build
+.vinxi
+# Sentry Config File
+.env.sentry-build-plugin
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/e2e/start/website/.prettierignore b/e2e/start/website/.prettierignore
new file mode 100644
index 0000000000..2be5eaa6ec
--- /dev/null
+++ b/e2e/start/website/.prettierignore
@@ -0,0 +1,4 @@
+**/build
+**/public
+pnpm-lock.yaml
+routeTree.gen.ts
\ No newline at end of file
diff --git a/e2e/start/website/app.config.ts b/e2e/start/website/app.config.ts
new file mode 100644
index 0000000000..732f04eabe
--- /dev/null
+++ b/e2e/start/website/app.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from '@tanstack/start/config'
+import tsConfigPaths from 'vite-tsconfig-paths'
+
+export default defineConfig({
+ vite: {
+ plugins: [
+ tsConfigPaths({
+ projects: ['./tsconfig.json'],
+ }),
+ ],
+ },
+})
diff --git a/e2e/start/website/app/client.tsx b/e2e/start/website/app/client.tsx
new file mode 100644
index 0000000000..7bde1ff01e
--- /dev/null
+++ b/e2e/start/website/app/client.tsx
@@ -0,0 +1,8 @@
+///
+import { hydrateRoot } from 'react-dom/client'
+import { StartClient } from '@tanstack/start'
+import { createRouter } from './router'
+
+const router = createRouter()
+
+hydrateRoot(document.getElementById('root')!, )
diff --git a/e2e/start/website/app/components/DefaultCatchBoundary.tsx b/e2e/start/website/app/components/DefaultCatchBoundary.tsx
new file mode 100644
index 0000000000..15f316681c
--- /dev/null
+++ b/e2e/start/website/app/components/DefaultCatchBoundary.tsx
@@ -0,0 +1,53 @@
+import {
+ ErrorComponent,
+ Link,
+ rootRouteId,
+ useMatch,
+ useRouter,
+} from '@tanstack/react-router'
+import type { ErrorComponentProps } from '@tanstack/react-router'
+
+export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
+ const router = useRouter()
+ const isRoot = useMatch({
+ strict: false,
+ select: (state) => state.id === rootRouteId,
+ })
+
+ console.error(error)
+
+ return (
+
+
+
+
+ {isRoot ? (
+
+ Home
+
+ ) : (
+ {
+ e.preventDefault()
+ window.history.back()
+ }}
+ >
+ Go Back
+
+ )}
+
+
+ )
+}
diff --git a/e2e/start/website/app/components/NotFound.tsx b/e2e/start/website/app/components/NotFound.tsx
new file mode 100644
index 0000000000..7b54fa5680
--- /dev/null
+++ b/e2e/start/website/app/components/NotFound.tsx
@@ -0,0 +1,25 @@
+import { Link } from '@tanstack/react-router'
+
+export function NotFound({ children }: { children?: any }) {
+ return (
+
+
+ {children ||
The page you are looking for does not exist.
}
+
+
+
+
+ Start Over
+
+
+
+ )
+}
diff --git a/e2e/start/website/app/routeTree.gen.ts b/e2e/start/website/app/routeTree.gen.ts
new file mode 100644
index 0000000000..149847ed30
--- /dev/null
+++ b/e2e/start/website/app/routeTree.gen.ts
@@ -0,0 +1,372 @@
+/* prettier-ignore-start */
+
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file is auto-generated by TanStack Router
+
+// Import Routes
+
+import { Route as rootRoute } from './routes/__root'
+import { Route as LibraryImport } from './routes/_library'
+import { Route as LibraryIndexImport } from './routes/_library.index'
+import { Route as ProjectIndexImport } from './routes/$project.index'
+import { Route as LibraryProjectImport } from './routes/_library.$project'
+import { Route as LibraryProjectVersionIndexImport } from './routes/_library.$project.$version.index'
+import { Route as ProjectVersionDocsIndexImport } from './routes/$project.$version.docs.index'
+import { Route as ProjectVersionDocsFrameworkFrameworkImport } from './routes/$project.$version.docs.framework.$framework'
+import { Route as ProjectVersionDocsFrameworkFrameworkIndexImport } from './routes/$project.$version.docs.framework.$framework.index'
+import { Route as ProjectVersionDocsFrameworkFrameworkSplatImport } from './routes/$project.$version.docs.framework.$framework.$'
+import { Route as ProjectVersionDocsFrameworkFrameworkExamplesSplatImport } from './routes/$project.$version.docs.framework.$framework.examples.$'
+
+// Create/Update Routes
+
+const LibraryRoute = LibraryImport.update({
+ id: '/_library',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const LibraryIndexRoute = LibraryIndexImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => LibraryRoute,
+} as any)
+
+const ProjectIndexRoute = ProjectIndexImport.update({
+ id: '/$project/',
+ path: '/$project/',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const LibraryProjectRoute = LibraryProjectImport.update({
+ id: '/$project',
+ path: '/$project',
+ getParentRoute: () => LibraryRoute,
+} as any)
+
+const LibraryProjectVersionIndexRoute = LibraryProjectVersionIndexImport.update(
+ {
+ id: '/$version/',
+ path: '/$version/',
+ getParentRoute: () => LibraryProjectRoute,
+ } as any,
+)
+
+const ProjectVersionDocsIndexRoute = ProjectVersionDocsIndexImport.update({
+ id: '/$project/$version/docs/',
+ path: '/$project/$version/docs/',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const ProjectVersionDocsFrameworkFrameworkRoute =
+ ProjectVersionDocsFrameworkFrameworkImport.update({
+ id: '/$project/$version/docs/framework/$framework',
+ path: '/$project/$version/docs/framework/$framework',
+ getParentRoute: () => rootRoute,
+ } as any)
+
+const ProjectVersionDocsFrameworkFrameworkIndexRoute =
+ ProjectVersionDocsFrameworkFrameworkIndexImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute,
+ } as any)
+
+const ProjectVersionDocsFrameworkFrameworkSplatRoute =
+ ProjectVersionDocsFrameworkFrameworkSplatImport.update({
+ id: '/$',
+ path: '/$',
+ getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute,
+ } as any)
+
+const ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute =
+ ProjectVersionDocsFrameworkFrameworkExamplesSplatImport.update({
+ id: '/examples/$',
+ path: '/examples/$',
+ getParentRoute: () => ProjectVersionDocsFrameworkFrameworkRoute,
+ } as any)
+
+// Populate the FileRoutesByPath interface
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/_library': {
+ id: '/_library'
+ path: ''
+ fullPath: ''
+ preLoaderRoute: typeof LibraryImport
+ parentRoute: typeof rootRoute
+ }
+ '/_library/$project': {
+ id: '/_library/$project'
+ path: '/$project'
+ fullPath: '/$project'
+ preLoaderRoute: typeof LibraryProjectImport
+ parentRoute: typeof LibraryImport
+ }
+ '/$project/': {
+ id: '/$project/'
+ path: '/$project'
+ fullPath: '/$project'
+ preLoaderRoute: typeof ProjectIndexImport
+ parentRoute: typeof rootRoute
+ }
+ '/_library/': {
+ id: '/_library/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof LibraryIndexImport
+ parentRoute: typeof LibraryImport
+ }
+ '/$project/$version/docs/': {
+ id: '/$project/$version/docs/'
+ path: '/$project/$version/docs'
+ fullPath: '/$project/$version/docs'
+ preLoaderRoute: typeof ProjectVersionDocsIndexImport
+ parentRoute: typeof rootRoute
+ }
+ '/_library/$project/$version/': {
+ id: '/_library/$project/$version/'
+ path: '/$version'
+ fullPath: '/$project/$version'
+ preLoaderRoute: typeof LibraryProjectVersionIndexImport
+ parentRoute: typeof LibraryProjectImport
+ }
+ '/$project/$version/docs/framework/$framework': {
+ id: '/$project/$version/docs/framework/$framework'
+ path: '/$project/$version/docs/framework/$framework'
+ fullPath: '/$project/$version/docs/framework/$framework'
+ preLoaderRoute: typeof ProjectVersionDocsFrameworkFrameworkImport
+ parentRoute: typeof rootRoute
+ }
+ '/$project/$version/docs/framework/$framework/$': {
+ id: '/$project/$version/docs/framework/$framework/$'
+ path: '/$'
+ fullPath: '/$project/$version/docs/framework/$framework/$'
+ preLoaderRoute: typeof ProjectVersionDocsFrameworkFrameworkSplatImport
+ parentRoute: typeof ProjectVersionDocsFrameworkFrameworkImport
+ }
+ '/$project/$version/docs/framework/$framework/': {
+ id: '/$project/$version/docs/framework/$framework/'
+ path: '/'
+ fullPath: '/$project/$version/docs/framework/$framework/'
+ preLoaderRoute: typeof ProjectVersionDocsFrameworkFrameworkIndexImport
+ parentRoute: typeof ProjectVersionDocsFrameworkFrameworkImport
+ }
+ '/$project/$version/docs/framework/$framework/examples/$': {
+ id: '/$project/$version/docs/framework/$framework/examples/$'
+ path: '/examples/$'
+ fullPath: '/$project/$version/docs/framework/$framework/examples/$'
+ preLoaderRoute: typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatImport
+ parentRoute: typeof ProjectVersionDocsFrameworkFrameworkImport
+ }
+ }
+}
+
+// Create and export the route tree
+
+interface LibraryProjectRouteChildren {
+ LibraryProjectVersionIndexRoute: typeof LibraryProjectVersionIndexRoute
+}
+
+const LibraryProjectRouteChildren: LibraryProjectRouteChildren = {
+ LibraryProjectVersionIndexRoute: LibraryProjectVersionIndexRoute,
+}
+
+const LibraryProjectRouteWithChildren = LibraryProjectRoute._addFileChildren(
+ LibraryProjectRouteChildren,
+)
+
+interface LibraryRouteChildren {
+ LibraryProjectRoute: typeof LibraryProjectRouteWithChildren
+ LibraryIndexRoute: typeof LibraryIndexRoute
+}
+
+const LibraryRouteChildren: LibraryRouteChildren = {
+ LibraryProjectRoute: LibraryProjectRouteWithChildren,
+ LibraryIndexRoute: LibraryIndexRoute,
+}
+
+const LibraryRouteWithChildren =
+ LibraryRoute._addFileChildren(LibraryRouteChildren)
+
+interface ProjectVersionDocsFrameworkFrameworkRouteChildren {
+ ProjectVersionDocsFrameworkFrameworkSplatRoute: typeof ProjectVersionDocsFrameworkFrameworkSplatRoute
+ ProjectVersionDocsFrameworkFrameworkIndexRoute: typeof ProjectVersionDocsFrameworkFrameworkIndexRoute
+ ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute: typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute
+}
+
+const ProjectVersionDocsFrameworkFrameworkRouteChildren: ProjectVersionDocsFrameworkFrameworkRouteChildren =
+ {
+ ProjectVersionDocsFrameworkFrameworkSplatRoute:
+ ProjectVersionDocsFrameworkFrameworkSplatRoute,
+ ProjectVersionDocsFrameworkFrameworkIndexRoute:
+ ProjectVersionDocsFrameworkFrameworkIndexRoute,
+ ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute:
+ ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute,
+ }
+
+const ProjectVersionDocsFrameworkFrameworkRouteWithChildren =
+ ProjectVersionDocsFrameworkFrameworkRoute._addFileChildren(
+ ProjectVersionDocsFrameworkFrameworkRouteChildren,
+ )
+
+export interface FileRoutesByFullPath {
+ '': typeof LibraryRouteWithChildren
+ '/$project': typeof ProjectIndexRoute
+ '/': typeof LibraryIndexRoute
+ '/$project/$version/docs': typeof ProjectVersionDocsIndexRoute
+ '/$project/$version': typeof LibraryProjectVersionIndexRoute
+ '/$project/$version/docs/framework/$framework': typeof ProjectVersionDocsFrameworkFrameworkRouteWithChildren
+ '/$project/$version/docs/framework/$framework/$': typeof ProjectVersionDocsFrameworkFrameworkSplatRoute
+ '/$project/$version/docs/framework/$framework/': typeof ProjectVersionDocsFrameworkFrameworkIndexRoute
+ '/$project/$version/docs/framework/$framework/examples/$': typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute
+}
+
+export interface FileRoutesByTo {
+ '/$project': typeof ProjectIndexRoute
+ '/': typeof LibraryIndexRoute
+ '/$project/$version/docs': typeof ProjectVersionDocsIndexRoute
+ '/$project/$version': typeof LibraryProjectVersionIndexRoute
+ '/$project/$version/docs/framework/$framework/$': typeof ProjectVersionDocsFrameworkFrameworkSplatRoute
+ '/$project/$version/docs/framework/$framework': typeof ProjectVersionDocsFrameworkFrameworkIndexRoute
+ '/$project/$version/docs/framework/$framework/examples/$': typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute
+}
+
+export interface FileRoutesById {
+ __root__: typeof rootRoute
+ '/_library': typeof LibraryRouteWithChildren
+ '/_library/$project': typeof LibraryProjectRouteWithChildren
+ '/$project/': typeof ProjectIndexRoute
+ '/_library/': typeof LibraryIndexRoute
+ '/$project/$version/docs/': typeof ProjectVersionDocsIndexRoute
+ '/_library/$project/$version/': typeof LibraryProjectVersionIndexRoute
+ '/$project/$version/docs/framework/$framework': typeof ProjectVersionDocsFrameworkFrameworkRouteWithChildren
+ '/$project/$version/docs/framework/$framework/$': typeof ProjectVersionDocsFrameworkFrameworkSplatRoute
+ '/$project/$version/docs/framework/$framework/': typeof ProjectVersionDocsFrameworkFrameworkIndexRoute
+ '/$project/$version/docs/framework/$framework/examples/$': typeof ProjectVersionDocsFrameworkFrameworkExamplesSplatRoute
+}
+
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | ''
+ | '/$project'
+ | '/'
+ | '/$project/$version/docs'
+ | '/$project/$version'
+ | '/$project/$version/docs/framework/$framework'
+ | '/$project/$version/docs/framework/$framework/$'
+ | '/$project/$version/docs/framework/$framework/'
+ | '/$project/$version/docs/framework/$framework/examples/$'
+ fileRoutesByTo: FileRoutesByTo
+ to:
+ | '/$project'
+ | '/'
+ | '/$project/$version/docs'
+ | '/$project/$version'
+ | '/$project/$version/docs/framework/$framework/$'
+ | '/$project/$version/docs/framework/$framework'
+ | '/$project/$version/docs/framework/$framework/examples/$'
+ id:
+ | '__root__'
+ | '/_library'
+ | '/_library/$project'
+ | '/$project/'
+ | '/_library/'
+ | '/$project/$version/docs/'
+ | '/_library/$project/$version/'
+ | '/$project/$version/docs/framework/$framework'
+ | '/$project/$version/docs/framework/$framework/$'
+ | '/$project/$version/docs/framework/$framework/'
+ | '/$project/$version/docs/framework/$framework/examples/$'
+ fileRoutesById: FileRoutesById
+}
+
+export interface RootRouteChildren {
+ LibraryRoute: typeof LibraryRouteWithChildren
+ ProjectIndexRoute: typeof ProjectIndexRoute
+ ProjectVersionDocsIndexRoute: typeof ProjectVersionDocsIndexRoute
+ ProjectVersionDocsFrameworkFrameworkRoute: typeof ProjectVersionDocsFrameworkFrameworkRouteWithChildren
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ LibraryRoute: LibraryRouteWithChildren,
+ ProjectIndexRoute: ProjectIndexRoute,
+ ProjectVersionDocsIndexRoute: ProjectVersionDocsIndexRoute,
+ ProjectVersionDocsFrameworkFrameworkRoute:
+ ProjectVersionDocsFrameworkFrameworkRouteWithChildren,
+}
+
+export const routeTree = rootRoute
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+/* prettier-ignore-end */
+
+/* ROUTE_MANIFEST_START
+{
+ "routes": {
+ "__root__": {
+ "filePath": "__root.tsx",
+ "children": [
+ "/_library",
+ "/$project/",
+ "/$project/$version/docs/",
+ "/$project/$version/docs/framework/$framework"
+ ]
+ },
+ "/_library": {
+ "filePath": "_library.tsx",
+ "children": [
+ "/_library/$project",
+ "/_library/"
+ ]
+ },
+ "/_library/$project": {
+ "filePath": "_library.$project.tsx",
+ "parent": "/_library",
+ "children": [
+ "/_library/$project/$version/"
+ ]
+ },
+ "/$project/": {
+ "filePath": "$project.index.tsx"
+ },
+ "/_library/": {
+ "filePath": "_library.index.tsx",
+ "parent": "/_library"
+ },
+ "/$project/$version/docs/": {
+ "filePath": "$project.$version.docs.index.tsx"
+ },
+ "/_library/$project/$version/": {
+ "filePath": "_library.$project.$version.index.tsx",
+ "parent": "/_library/$project"
+ },
+ "/$project/$version/docs/framework/$framework": {
+ "filePath": "$project.$version.docs.framework.$framework.tsx",
+ "children": [
+ "/$project/$version/docs/framework/$framework/$",
+ "/$project/$version/docs/framework/$framework/",
+ "/$project/$version/docs/framework/$framework/examples/$"
+ ]
+ },
+ "/$project/$version/docs/framework/$framework/$": {
+ "filePath": "$project.$version.docs.framework.$framework.$.tsx",
+ "parent": "/$project/$version/docs/framework/$framework"
+ },
+ "/$project/$version/docs/framework/$framework/": {
+ "filePath": "$project.$version.docs.framework.$framework.index.tsx",
+ "parent": "/$project/$version/docs/framework/$framework"
+ },
+ "/$project/$version/docs/framework/$framework/examples/$": {
+ "filePath": "$project.$version.docs.framework.$framework.examples.$.tsx",
+ "parent": "/$project/$version/docs/framework/$framework"
+ }
+ }
+}
+ROUTE_MANIFEST_END */
diff --git a/e2e/start/website/app/router.tsx b/e2e/start/website/app/router.tsx
new file mode 100644
index 0000000000..822b1463cc
--- /dev/null
+++ b/e2e/start/website/app/router.tsx
@@ -0,0 +1,22 @@
+import { createRouter as createTanStackRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
+import { NotFound } from './components/NotFound'
+
+export function createRouter() {
+ const router = createTanStackRouter({
+ routeTree,
+ defaultPreload: 'intent',
+ defaultStaleTime: 5000,
+ defaultErrorComponent: DefaultCatchBoundary,
+ defaultNotFoundComponent: () => ,
+ })
+
+ return router
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.$.tsx b/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.$.tsx
new file mode 100644
index 0000000000..b38bebc812
--- /dev/null
+++ b/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.$.tsx
@@ -0,0 +1,40 @@
+import { ErrorComponent, createFileRoute } from '@tanstack/react-router'
+import type { ErrorComponentProps } from '@tanstack/react-router'
+import { NotFound } from '~/components/NotFound'
+import { getDocument } from '~/server/document'
+import { capitalize, seo } from '~/utils/seo'
+
+export const Route = createFileRoute(
+ '/$project/$version/docs/framework/$framework/$',
+)({
+ loader: ({ params: { _splat } }) => getDocument(_splat!),
+ meta: ({ loaderData, params }) =>
+ seo({
+ title: `${loaderData.title} | TanStack ${capitalize(params.project)} ${capitalize(params.framework)}`,
+ }),
+ errorComponent: PostErrorComponent,
+ component: Page,
+ notFoundComponent: () => {
+ return Document not found
+ },
+})
+
+function PostErrorComponent({ error }: ErrorComponentProps) {
+ return
+}
+
+function Page() {
+ const post = Route.useLoaderData()
+
+ return (
+
+
+ {post.title}
+
+
{post.content}
+
+ )
+}
diff --git a/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.examples.$.tsx b/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.examples.$.tsx
new file mode 100644
index 0000000000..a06b4ec1b3
--- /dev/null
+++ b/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.examples.$.tsx
@@ -0,0 +1,31 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { NotFound } from '~/components/NotFound'
+import { capitalize, seo } from '~/utils/seo'
+
+export const Route = createFileRoute(
+ '/$project/$version/docs/framework/$framework/examples/$',
+)({
+ meta: ({ params }) =>
+ seo({
+ title: `${capitalize(params._splat || '')} Example | TanStack ${capitalize(params.project)} ${capitalize(params.framework)}`,
+ }),
+ component: Page,
+ notFoundComponent: () => {
+ return Example not found
+ },
+})
+
+function Page() {
+ const params = Route.useParams()
+
+ return (
+
+
+ {params._splat} example
+
+
+ )
+}
diff --git a/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.index.tsx b/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.index.tsx
new file mode 100644
index 0000000000..64a30c1ee1
--- /dev/null
+++ b/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.index.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute(
+ '/$project/$version/docs/framework/$framework/',
+)({
+ loader: () => {
+ throw redirect({
+ from: '/$project/$version/docs/framework/$framework/',
+ to: '/$project/$version/docs/framework/$framework/$',
+ params: {
+ _splat: 'overview',
+ },
+ })
+ },
+})
diff --git a/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.tsx b/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.tsx
new file mode 100644
index 0000000000..8a614e51c8
--- /dev/null
+++ b/e2e/start/website/app/routes/$project.$version.docs.framework.$framework.tsx
@@ -0,0 +1,123 @@
+import {
+ Link,
+ Outlet,
+ createFileRoute,
+ useLocation,
+} from '@tanstack/react-router'
+import { getDocumentHeads } from '~/server/document'
+import { getProject } from '~/server/projects'
+
+export const Route = createFileRoute(
+ '/$project/$version/docs/framework/$framework',
+)({
+ loader: async ({ params: { project } }) => {
+ const library = await getProject(project)
+ const documents = await getDocumentHeads()
+ return {
+ library,
+ documents,
+ }
+ },
+ component: Page,
+})
+
+function Page() {
+ const project = Route.useLoaderData({ select: (s) => s.library })
+ const documents = Route.useLoaderData({ select: (s) => s.documents })
+ const pathname = useLocation({ select: (s) => s.pathname })
+
+ return (
+
+
+
+
+ {pathname}
+
+
+
+
+ )
+}
diff --git a/e2e/start/website/app/routes/$project.$version.docs.index.tsx b/e2e/start/website/app/routes/$project.$version.docs.index.tsx
new file mode 100644
index 0000000000..e379d81908
--- /dev/null
+++ b/e2e/start/website/app/routes/$project.$version.docs.index.tsx
@@ -0,0 +1,14 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/$project/$version/docs/')({
+ loader: () => {
+ throw redirect({
+ from: '/$project/$version/docs',
+ to: '/$project/$version/docs/framework/$framework/$',
+ params: {
+ framework: 'react',
+ _splat: 'overview',
+ },
+ })
+ },
+})
diff --git a/e2e/start/website/app/routes/$project.index.tsx b/e2e/start/website/app/routes/$project.index.tsx
new file mode 100644
index 0000000000..9c6b935e83
--- /dev/null
+++ b/e2e/start/website/app/routes/$project.index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/$project/')({
+ loader: ({ params }) => {
+ throw redirect({
+ to: '/$project/$version',
+ params: {
+ project: params.project,
+ version: 'latest',
+ },
+ })
+ },
+})
diff --git a/e2e/start/website/app/routes/__root.tsx b/e2e/start/website/app/routes/__root.tsx
new file mode 100644
index 0000000000..4f3568e517
--- /dev/null
+++ b/e2e/start/website/app/routes/__root.tsx
@@ -0,0 +1,83 @@
+import {
+ Outlet,
+ ScrollRestoration,
+ createRootRoute,
+} from '@tanstack/react-router'
+import { TanStackRouterDevtools } from '@tanstack/router-devtools'
+import { Body, Head, Html, Meta, Scripts } from '@tanstack/start'
+import * as React from 'react'
+import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
+import { NotFound } from '~/components/NotFound'
+import appCss from '~/styles/app.css?url'
+import { seo } from '~/utils/seo'
+
+export const Route = createRootRoute({
+ meta: () => [
+ {
+ charSet: 'utf-8',
+ },
+ {
+ name: 'viewport',
+ content: 'width=device-width, initial-scale=1',
+ },
+ ...seo({
+ title: 'TanStack Website',
+ description: `TanStack projects are type-safe!!!`,
+ }),
+ ],
+ links: () => [
+ { rel: 'stylesheet', href: appCss },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '180x180',
+ href: '/apple-touch-icon.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '32x32',
+ href: '/favicon-32x32.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '16x16',
+ href: '/favicon-16x16.png',
+ },
+ { rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
+ { rel: 'icon', href: '/favicon.ico' },
+ ],
+ errorComponent: (props) => {
+ return (
+
+
+
+ )
+ },
+ notFoundComponent: () => ,
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+ )
+}
+
+function RootDocument({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+
+ )
+}
diff --git a/e2e/start/website/app/routes/_library.$project.$version.index.tsx b/e2e/start/website/app/routes/_library.$project.$version.index.tsx
new file mode 100644
index 0000000000..d91b72d2b7
--- /dev/null
+++ b/e2e/start/website/app/routes/_library.$project.$version.index.tsx
@@ -0,0 +1,23 @@
+import { Link, createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/_library/$project/$version/')({
+ component: Page,
+})
+
+function Page() {
+ const params = Route.useParams()
+
+ return (
+
+
+ {params.project} landing page
+
+
version: {params.version}
+
+
+ Get started with our documentation.
+
+
+
+ )
+}
diff --git a/e2e/start/website/app/routes/_library.$project.tsx b/e2e/start/website/app/routes/_library.$project.tsx
new file mode 100644
index 0000000000..817728c173
--- /dev/null
+++ b/e2e/start/website/app/routes/_library.$project.tsx
@@ -0,0 +1,13 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+import { getProject } from '~/server/projects'
+import { seo } from '~/utils/seo'
+
+export const Route = createFileRoute('/_library/$project')({
+ loader: ({ params: { project } }) => getProject(project),
+ meta: ({ loaderData }) => seo({ title: `TanStack ${loaderData.name}` }),
+ component: () => (
+
+
+
+ ),
+})
diff --git a/e2e/start/website/app/routes/_library.index.tsx b/e2e/start/website/app/routes/_library.index.tsx
new file mode 100644
index 0000000000..f08b6c633a
--- /dev/null
+++ b/e2e/start/website/app/routes/_library.index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/_library/')({
+ component: Home,
+})
+
+function Home() {
+ return (
+
+
Website Landing Page
+
+ )
+}
diff --git a/e2e/start/website/app/routes/_library.tsx b/e2e/start/website/app/routes/_library.tsx
new file mode 100644
index 0000000000..e120515350
--- /dev/null
+++ b/e2e/start/website/app/routes/_library.tsx
@@ -0,0 +1,62 @@
+import {
+ Link,
+ Outlet,
+ createFileRoute,
+ useLocation,
+} from '@tanstack/react-router'
+import { getProjects } from '~/server/projects'
+
+export const Route = createFileRoute('/_library')({
+ loader: async () => {
+ const projects = await getProjects()
+ return {
+ libraries: projects,
+ }
+ },
+ component: Layout,
+})
+
+function Layout() {
+ const { libraries } = Route.useLoaderData()
+ const pathname = useLocation({ select: (s) => s.pathname })
+ return (
+
+
+
+
+ {pathname}
+
+
+
+
+ )
+}
diff --git a/e2e/start/website/app/server/document.tsx b/e2e/start/website/app/server/document.tsx
new file mode 100644
index 0000000000..759c63920e
--- /dev/null
+++ b/e2e/start/website/app/server/document.tsx
@@ -0,0 +1,51 @@
+import { notFound } from '@tanstack/react-router'
+import { createServerFn } from '@tanstack/start'
+
+const documents: Array<{ id: string; title: string; content: string }> = [
+ {
+ id: 'overview',
+ title: 'Overview',
+ content: 'This is the content of the overview document',
+ },
+ {
+ id: 'getting-started',
+ title: 'Getting Started',
+ content: 'To get started, you need to do the following...',
+ },
+ {
+ id: 'installation',
+ title: 'Installation',
+ content: 'To install this package, run the following command...',
+ },
+ {
+ id: 'ref/useQueryFunction',
+ title: 'useQuery Reference',
+ content: 'The useQuery function is used to...',
+ },
+ {
+ id: 'ref/useMutationFunction',
+ title: 'useMutation Reference',
+ content: 'The useMutation function is used to...',
+ },
+]
+
+export const getDocumentHeads = createServerFn('GET', async () => {
+ await new Promise((resolve) => setTimeout(resolve, 200))
+
+ return documents.map(({ id, title }) => ({
+ id,
+ title,
+ }))
+})
+
+export const getDocument = createServerFn('GET', async (id: string) => {
+ await new Promise((resolve) => setTimeout(resolve, 200))
+
+ const document = documents.find((doc) => doc.id === id)
+
+ if (!document) {
+ throw notFound()
+ }
+
+ return document
+})
diff --git a/e2e/start/website/app/server/projects.tsx b/e2e/start/website/app/server/projects.tsx
new file mode 100644
index 0000000000..b76bf05feb
--- /dev/null
+++ b/e2e/start/website/app/server/projects.tsx
@@ -0,0 +1,29 @@
+import { createServerFn } from '@tanstack/start'
+import { notFound } from '@tanstack/react-router'
+import { capitalize } from '~/utils/seo'
+
+const projects = ['router', 'table', 'query', 'form', 'ranger']
+
+export const getProjects = createServerFn('GET', async () => {
+ await new Promise((resolve) => setTimeout(resolve, 200))
+
+ return projects
+})
+
+export const getProject = createServerFn('GET', async (project: string) => {
+ await new Promise((resolve) => setTimeout(resolve, 200))
+
+ const selectedProject = projects.find((p) => p === project.toLowerCase())
+
+ if (!selectedProject) {
+ throw notFound()
+ }
+
+ return {
+ id: selectedProject,
+ name: capitalize(selectedProject),
+ versions: ['latest', 'v2', 'v1'],
+ frameworks: ['react', 'vue', 'solidjs', 'svelte'],
+ examples: ['basic', 'kitchen-sink'],
+ }
+})
diff --git a/e2e/start/website/app/ssr.tsx b/e2e/start/website/app/ssr.tsx
new file mode 100644
index 0000000000..f2d33f9030
--- /dev/null
+++ b/e2e/start/website/app/ssr.tsx
@@ -0,0 +1,13 @@
+///
+import {
+ createStartHandler,
+ defaultStreamHandler,
+} from '@tanstack/start/server'
+import { getRouterManifest } from '@tanstack/start/router-manifest'
+
+import { createRouter } from './router'
+
+export default createStartHandler({
+ createRouter,
+ getRouterManifest,
+})(defaultStreamHandler)
diff --git a/e2e/start/website/app/styles/app.css b/e2e/start/website/app/styles/app.css
new file mode 100644
index 0000000000..d6426ccb72
--- /dev/null
+++ b/e2e/start/website/app/styles/app.css
@@ -0,0 +1,14 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ html,
+ body {
+ @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200;
+ }
+
+ .using-mouse * {
+ outline: none !important;
+ }
+}
diff --git a/e2e/start/website/app/utils/seo.ts b/e2e/start/website/app/utils/seo.ts
new file mode 100644
index 0000000000..82cf2aec07
--- /dev/null
+++ b/e2e/start/website/app/utils/seo.ts
@@ -0,0 +1,36 @@
+export const seo = ({
+ title,
+ description,
+ keywords,
+ image,
+}: {
+ title: string
+ description?: string
+ image?: string
+ keywords?: string
+}) => {
+ const tags = [
+ { title },
+ { name: 'description', content: description },
+ { name: 'keywords', content: keywords },
+ { name: 'twitter:title', content: title },
+ { name: 'twitter:description', content: description },
+ { name: 'twitter:creator', content: '@tannerlinsley' },
+ { name: 'twitter:site', content: '@tannerlinsley' },
+ { name: 'og:type', content: 'website' },
+ { name: 'og:title', content: title },
+ { name: 'og:description', content: description },
+ ...(image
+ ? [
+ { name: 'twitter:image', content: image },
+ { name: 'twitter:card', content: 'summary_large_image' },
+ { name: 'og:image', content: image },
+ ]
+ : []),
+ ]
+
+ return tags
+}
+
+export const capitalize = (str: string) =>
+ str.charAt(0).toUpperCase() + str.slice(1)
diff --git a/e2e/start/website/package.json b/e2e/start/website/package.json
new file mode 100644
index 0000000000..f88b0c60c4
--- /dev/null
+++ b/e2e/start/website/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "tanstack-start-e2e-website",
+ "private": true,
+ "sideEffects": false,
+ "type": "module",
+ "scripts": {
+ "dev": "vinxi dev --port 3010",
+ "build": "vinxi build",
+ "start": "vinxi start",
+ "test:e2e": "playwright test --project=chromium"
+ },
+ "dependencies": {
+ "@tanstack/react-router": "workspace:^",
+ "@tanstack/router-devtools": "workspace:^",
+ "@tanstack/start": "workspace:^",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "redaxios": "^0.5.1",
+ "tailwind-merge": "^2.5.4",
+ "vinxi": "0.4.3",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.48.2",
+ "@types/node": "^22.5.4",
+ "@types/react": "^18.2.65",
+ "@types/react-dom": "^18.2.21",
+ "@vitejs/plugin-react": "^4.3.3",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.47",
+ "tailwindcss": "^3.4.14",
+ "typescript": "^5.6.2",
+ "vite-tsconfig-paths": "^5.0.1"
+ }
+}
diff --git a/e2e/start/website/playwright.config.ts b/e2e/start/website/playwright.config.ts
new file mode 100644
index 0000000000..5fbb3e3429
--- /dev/null
+++ b/e2e/start/website/playwright.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig, devices } from '@playwright/test'
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './tests',
+
+ reporter: [['line']],
+
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: 'http://localhost:3010/',
+ },
+
+ webServer: {
+ // TODO: build && start seems broken, use that if it's working
+ command: 'pnpm run dev',
+ url: 'http://localhost:3010',
+ reuseExistingServer: !process.env.CI,
+ stdout: 'pipe',
+ },
+
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+})
diff --git a/e2e/start/website/postcss.config.cjs b/e2e/start/website/postcss.config.cjs
new file mode 100644
index 0000000000..8e638a6bcd
--- /dev/null
+++ b/e2e/start/website/postcss.config.cjs
@@ -0,0 +1,7 @@
+module.exports = {
+ plugins: [
+ require('tailwindcss/nesting'),
+ require('tailwindcss'),
+ require('autoprefixer'),
+ ],
+}
diff --git a/e2e/start/website/public/android-chrome-192x192.png b/e2e/start/website/public/android-chrome-192x192.png
new file mode 100644
index 0000000000..09c8324f8c
Binary files /dev/null and b/e2e/start/website/public/android-chrome-192x192.png differ
diff --git a/e2e/start/website/public/android-chrome-512x512.png b/e2e/start/website/public/android-chrome-512x512.png
new file mode 100644
index 0000000000..11d626ea3d
Binary files /dev/null and b/e2e/start/website/public/android-chrome-512x512.png differ
diff --git a/e2e/start/website/public/apple-touch-icon.png b/e2e/start/website/public/apple-touch-icon.png
new file mode 100644
index 0000000000..5a9423cc02
Binary files /dev/null and b/e2e/start/website/public/apple-touch-icon.png differ
diff --git a/e2e/start/website/public/favicon-16x16.png b/e2e/start/website/public/favicon-16x16.png
new file mode 100644
index 0000000000..e3389b0044
Binary files /dev/null and b/e2e/start/website/public/favicon-16x16.png differ
diff --git a/e2e/start/website/public/favicon-32x32.png b/e2e/start/website/public/favicon-32x32.png
new file mode 100644
index 0000000000..900c77d444
Binary files /dev/null and b/e2e/start/website/public/favicon-32x32.png differ
diff --git a/e2e/start/website/public/favicon.ico b/e2e/start/website/public/favicon.ico
new file mode 100644
index 0000000000..1a1751676f
Binary files /dev/null and b/e2e/start/website/public/favicon.ico differ
diff --git a/e2e/start/website/public/favicon.png b/e2e/start/website/public/favicon.png
new file mode 100644
index 0000000000..1e77bc0609
Binary files /dev/null and b/e2e/start/website/public/favicon.png differ
diff --git a/e2e/start/website/public/site.webmanifest b/e2e/start/website/public/site.webmanifest
new file mode 100644
index 0000000000..fa99de77db
--- /dev/null
+++ b/e2e/start/website/public/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/e2e/start/website/tailwind.config.cjs b/e2e/start/website/tailwind.config.cjs
new file mode 100644
index 0000000000..75fe25dbf7
--- /dev/null
+++ b/e2e/start/website/tailwind.config.cjs
@@ -0,0 +1,4 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: ['./app/**/*.{js,ts,jsx,tsx}'],
+}
diff --git a/e2e/start/website/tests/app.spec.ts b/e2e/start/website/tests/app.spec.ts
new file mode 100644
index 0000000000..f115180956
--- /dev/null
+++ b/e2e/start/website/tests/app.spec.ts
@@ -0,0 +1,19 @@
+import { expect, test } from '@playwright/test'
+
+const routeTestId = 'selected-route-label'
+
+test('resolves to the latest version on load of a project like "/router"', async ({
+ page,
+}) => {
+ await page.goto('/router')
+
+ await expect(page.getByTestId(routeTestId)).toContainText('/router/latest')
+})
+
+test('resolves to the overview docs page', async ({ page }) => {
+ await page.goto('/router/latest/docs')
+
+ await expect(page.getByTestId(routeTestId)).toContainText(
+ '/router/latest/docs/framework/react/overview',
+ )
+})
diff --git a/e2e/start/website/tsconfig.json b/e2e/start/website/tsconfig.json
new file mode 100644
index 0000000000..d1b5b77660
--- /dev/null
+++ b/e2e/start/website/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "include": ["**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "strict": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "isolatedModules": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "target": "ES2022",
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+ "noEmit": true
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d9473bc961..5a6ca79d42 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -777,6 +777,67 @@ importers:
specifier: ^5.0.1
version: 5.0.1(typescript@5.6.3)(vite@5.4.10(@types/node@22.7.4)(terser@5.31.1))
+ e2e/start/website:
+ dependencies:
+ '@tanstack/react-router':
+ specifier: workspace:*
+ version: link:../../../packages/react-router
+ '@tanstack/router-devtools':
+ specifier: workspace:*
+ version: link:../../../packages/router-devtools
+ '@tanstack/start':
+ specifier: workspace:*
+ version: link:../../../packages/start
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ redaxios:
+ specifier: ^0.5.1
+ version: 0.5.1
+ tailwind-merge:
+ specifier: ^2.5.4
+ version: 2.5.4
+ vinxi:
+ specifier: 0.4.3
+ version: 0.4.3(@types/node@22.7.4)(ioredis@5.4.1)(terser@5.31.1)(webpack-sources@3.2.3)
+ zod:
+ specifier: ^3.23.8
+ version: 3.23.8
+ devDependencies:
+ '@playwright/test':
+ specifier: ^1.48.2
+ version: 1.48.2
+ '@types/node':
+ specifier: ^22.5.4
+ version: 22.7.4
+ '@types/react':
+ specifier: ^18.2.65
+ version: 18.3.11
+ '@types/react-dom':
+ specifier: ^18.2.21
+ version: 18.3.0
+ '@vitejs/plugin-react':
+ specifier: ^4.3.3
+ version: 4.3.3(vite@5.4.10(@types/node@22.7.4)(terser@5.31.1))
+ autoprefixer:
+ specifier: ^10.4.20
+ version: 10.4.20(postcss@8.4.47)
+ postcss:
+ specifier: ^8.4.47
+ version: 8.4.47
+ tailwindcss:
+ specifier: ^3.4.14
+ version: 3.4.14
+ typescript:
+ specifier: ^5.6.2
+ version: 5.6.3
+ vite-tsconfig-paths:
+ specifier: ^5.0.1
+ version: 5.0.1(typescript@5.6.3)(vite@5.4.10(@types/node@22.7.4)(terser@5.31.1))
+
examples/react/authenticated-routes:
dependencies:
'@tanstack/react-router':