diff --git a/e2e/react-start/custom-basepath/package.json b/e2e/react-start/custom-basepath/package.json
index cdb371b91f9..ff1c29bf6e6 100644
--- a/e2e/react-start/custom-basepath/package.json
+++ b/e2e/react-start/custom-basepath/package.json
@@ -6,9 +6,16 @@
"scripts": {
"dev": "cross-env NODE_ENV=development tsx express-server.ts",
"build": "vite build && tsc --noEmit",
+ "build:prerender": "cross-env MODE=prerender vite build && tsc --noEmit",
+ "build:prerender:trailing": "cross-env MODE=prerender TRAILING_SLASH=true vite build && tsc --noEmit",
"preview": "vite preview",
"start": "tsx express-server.ts",
- "test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
+ "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' &",
+ "test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'",
+ "test:e2e:prerender": "rm -rf port*.txt; cross-env MODE=prerender playwright test --project=chromium",
+ "test:e2e:prerender:trailing": "rm -rf port*.txt; cross-env MODE=prerender TRAILING_SLASH=true playwright test --project=chromium",
+ "test:e2e:ssrMode": "rm -rf port*.txt; playwright test --project=chromium",
+ "test:e2e": "pnpm run test:e2e:ssrMode && pnpm run test:e2e:prerender && pnpm run test:e2e:prerender:trailing"
},
"dependencies": {
"@tanstack/react-router": "workspace:^",
diff --git a/e2e/react-start/custom-basepath/playwright.config.ts b/e2e/react-start/custom-basepath/playwright.config.ts
index 5095425f473..17c013aaa6c 100644
--- a/e2e/react-start/custom-basepath/playwright.config.ts
+++ b/e2e/react-start/custom-basepath/playwright.config.ts
@@ -4,11 +4,36 @@ import {
getTestServerPort,
} from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }
+import { isPrerender } from './tests/utils/isPrerender'
const PORT = await getTestServerPort(packageJson.name)
+const START_PORT = await getTestServerPort(packageJson.name)
const EXTERNAL_PORT = await getDummyServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}/custom/basepath`
+const ssrModeCommand = `pnpm build && pnpm start`
+const prerenderModeCommand = `pnpm run test:e2e:startDummyServer && pnpm build:prerender && pnpm run test:e2e:stopDummyServer && pnpm start`
+const prerenderTrailingModeCommand = `pnpm run test:e2e:startDummyServer && pnpm build:prerender:trailing && pnpm run test:e2e:stopDummyServer && pnpm start`
+const isTrailingSlashPrerender =
+ process.env.TRAILING_SLASH?.toLowerCase() === 'true'
+
+const getCommand = () => {
+ if (isPrerender && isTrailingSlashPrerender)
+ return prerenderTrailingModeCommand
+ if (isPrerender) return prerenderModeCommand
+ return ssrModeCommand
+}
+
+console.log(
+ 'running in prerender mode: ',
+ isPrerender.toString(),
+ isPrerender
+ ? isTrailingSlashPrerender
+ ? 'with trailing slash'
+ : 'without trailing slash'
+ : '',
+)
+
/**
* See https://playwright.dev/docs/test-configuration.
*/
@@ -27,10 +52,20 @@ export default defineConfig({
},
webServer: {
- command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} PORT=${PORT} pnpm start`,
+ command: getCommand(),
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
+ env: {
+ MODE: process.env.MODE || '',
+ TRAILING_SLASH: isTrailingSlashPrerender.toString(),
+ VITE_TRAILING_SLASH: isTrailingSlashPrerender.toString(),
+ VITE_NODE_ENV: 'test',
+ VITE_EXTERNAL_PORT: String(EXTERNAL_PORT),
+ VITE_SERVER_PORT: String(PORT),
+ START_PORT: String(START_PORT),
+ PORT: String(PORT),
+ },
},
projects: [
diff --git a/e2e/react-start/custom-basepath/src/router.tsx b/e2e/react-start/custom-basepath/src/router.tsx
index 81b4c31daa8..62422fc2dfc 100644
--- a/e2e/react-start/custom-basepath/src/router.tsx
+++ b/e2e/react-start/custom-basepath/src/router.tsx
@@ -2,7 +2,6 @@ import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
import { NotFound } from './components/NotFound'
-import { basepath } from './utils/basepath'
export function getRouter() {
const router = createRouter({
@@ -11,7 +10,7 @@ export function getRouter() {
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: () => ,
scrollRestoration: true,
- basepath: basepath,
+ basepath: import.meta.env.BASE_URL,
})
return router
diff --git a/e2e/react-start/custom-basepath/src/routes/users.$userId.tsx b/e2e/react-start/custom-basepath/src/routes/users.$userId.tsx
index f811910df42..1741a80b939 100644
--- a/e2e/react-start/custom-basepath/src/routes/users.$userId.tsx
+++ b/e2e/react-start/custom-basepath/src/routes/users.$userId.tsx
@@ -1,15 +1,17 @@
import { ErrorComponent, createFileRoute } from '@tanstack/react-router'
import axios from 'redaxios'
+import { getRouterInstance } from '@tanstack/react-start'
import type { ErrorComponentProps } from '@tanstack/react-router'
-
import type { User } from '~/utils/users'
import { NotFound } from '~/components/NotFound'
-import { basepath } from '~/utils/basepath'
export const Route = createFileRoute('/users/$userId')({
loader: async ({ params: { userId } }) => {
+ const router = await getRouterInstance()
return await axios
- .get(basepath + '/api/users/' + userId)
+ .get(`/${router.options.basepath}/api/users/${userId}`, {
+ baseURL: router.origin,
+ })
.then((r) => r.data)
.catch(() => {
throw new Error('Failed to fetch user')
diff --git a/e2e/react-start/custom-basepath/src/routes/users.tsx b/e2e/react-start/custom-basepath/src/routes/users.tsx
index e78600349f7..28d1e7caff9 100644
--- a/e2e/react-start/custom-basepath/src/routes/users.tsx
+++ b/e2e/react-start/custom-basepath/src/routes/users.tsx
@@ -1,12 +1,15 @@
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
import axios from 'redaxios'
+import { getRouterInstance } from '@tanstack/react-start'
import type { User } from '~/utils/users'
-import { basepath } from '~/utils/basepath'
export const Route = createFileRoute('/users')({
loader: async () => {
+ const router = await getRouterInstance()
return await axios
- .get>(basepath + '/api/users')
+ .get>(`/${router.options.basepath}/api/users`, {
+ baseURL: router.origin,
+ })
.then((r) => r.data)
.catch(() => {
throw new Error('Failed to fetch users')
diff --git a/e2e/react-start/custom-basepath/src/utils/basepath.ts b/e2e/react-start/custom-basepath/src/utils/basepath.ts
deleted file mode 100644
index 6e719f196cf..00000000000
--- a/e2e/react-start/custom-basepath/src/utils/basepath.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const basepath = '/custom/basepath'
diff --git a/e2e/react-start/custom-basepath/tests/navigation.spec.ts b/e2e/react-start/custom-basepath/tests/navigation.spec.ts
index 3e6a5bd1bea..fba5af53848 100644
--- a/e2e/react-start/custom-basepath/tests/navigation.spec.ts
+++ b/e2e/react-start/custom-basepath/tests/navigation.spec.ts
@@ -66,7 +66,7 @@ test('server-side redirect', async ({ page, baseURL }) => {
.get('/redirect/throw-it', { maxRedirects: 0 })
.then((res) => {
const headers = new Headers(res.headers())
- expect(headers.get('location')).toBe('/custom/basepath/redirect/throw-it')
+ expect(headers.get('location')).toBe('/custom/basepath/posts/1')
})
await page.request
.get('/custom/basepath/redirect/throw-it', { maxRedirects: 0 })
diff --git a/e2e/react-start/custom-basepath/tests/utils/isPrerender.ts b/e2e/react-start/custom-basepath/tests/utils/isPrerender.ts
new file mode 100644
index 00000000000..d5d991d4545
--- /dev/null
+++ b/e2e/react-start/custom-basepath/tests/utils/isPrerender.ts
@@ -0,0 +1 @@
+export const isPrerender: boolean = process.env.MODE === 'prerender'
diff --git a/e2e/react-start/custom-basepath/vite.config.ts b/e2e/react-start/custom-basepath/vite.config.ts
index 75559f50a17..32a7643ca2b 100644
--- a/e2e/react-start/custom-basepath/vite.config.ts
+++ b/e2e/react-start/custom-basepath/vite.config.ts
@@ -3,9 +3,26 @@ import tsConfigPaths from 'vite-tsconfig-paths'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
+import { isPrerender } from './tests/utils/isPrerender'
+
+const prerenderConfiguration = {
+ enabled: true,
+ filter: (page: { path: string }) => {
+ return ![
+ '/i-do-not-exist',
+ '/posts',
+ '/redirect',
+ '/this-route-does-not-exist',
+ '/users',
+ ].some((p) => page.path.includes(p))
+ },
+ onSuccess: ({ page }: { page: { path: string } }) => {
+ console.log(`Rendered ${page.path}!`)
+ },
+}
export default defineConfig({
- base: '/custom/basepath',
+ base: `/custom/basepath${process.env.TRAILING_SLASH?.toLowerCase() === 'true' ? '/' : ''}`,
server: {
port: 3000,
},
@@ -16,6 +33,7 @@ export default defineConfig({
}),
tanstackStart({
vite: { installDevServerMiddleware: true },
+ prerender: isPrerender ? prerenderConfiguration : undefined,
}),
viteReact(),
],
diff --git a/e2e/solid-start/custom-basepath/package.json b/e2e/solid-start/custom-basepath/package.json
index e706b214533..eba00787c52 100644
--- a/e2e/solid-start/custom-basepath/package.json
+++ b/e2e/solid-start/custom-basepath/package.json
@@ -6,9 +6,16 @@
"scripts": {
"dev": "cross-env NODE_ENV=development tsx express-server.ts",
"build": "vite build && tsc --noEmit",
+ "build:prerender": "cross-env MODE=prerender vite build && tsc --noEmit",
+ "build:prerender:trailing": "cross-env MODE=prerender TRAILING_SLASH=true vite build && tsc --noEmit",
"preview": "vite preview",
"start": "tsx express-server.ts",
- "test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
+ "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' &",
+ "test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'",
+ "test:e2e:prerender": "rm -rf port*.txt; cross-env MODE=prerender playwright test --project=chromium",
+ "test:e2e:prerender:trailing": "rm -rf port*.txt; cross-env MODE=prerender TRAILING_SLASH=true playwright test --project=chromium",
+ "test:e2e:ssrMode": "rm -rf port*.txt; playwright test --project=chromium",
+ "test:e2e": "pnpm run test:e2e:ssrMode && pnpm run test:e2e:prerender && pnpm run test:e2e:prerender:trailing"
},
"dependencies": {
"@tanstack/solid-router": "workspace:^",
diff --git a/e2e/solid-start/custom-basepath/playwright.config.ts b/e2e/solid-start/custom-basepath/playwright.config.ts
index 5095425f473..17c013aaa6c 100644
--- a/e2e/solid-start/custom-basepath/playwright.config.ts
+++ b/e2e/solid-start/custom-basepath/playwright.config.ts
@@ -4,11 +4,36 @@ import {
getTestServerPort,
} from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }
+import { isPrerender } from './tests/utils/isPrerender'
const PORT = await getTestServerPort(packageJson.name)
+const START_PORT = await getTestServerPort(packageJson.name)
const EXTERNAL_PORT = await getDummyServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}/custom/basepath`
+const ssrModeCommand = `pnpm build && pnpm start`
+const prerenderModeCommand = `pnpm run test:e2e:startDummyServer && pnpm build:prerender && pnpm run test:e2e:stopDummyServer && pnpm start`
+const prerenderTrailingModeCommand = `pnpm run test:e2e:startDummyServer && pnpm build:prerender:trailing && pnpm run test:e2e:stopDummyServer && pnpm start`
+const isTrailingSlashPrerender =
+ process.env.TRAILING_SLASH?.toLowerCase() === 'true'
+
+const getCommand = () => {
+ if (isPrerender && isTrailingSlashPrerender)
+ return prerenderTrailingModeCommand
+ if (isPrerender) return prerenderModeCommand
+ return ssrModeCommand
+}
+
+console.log(
+ 'running in prerender mode: ',
+ isPrerender.toString(),
+ isPrerender
+ ? isTrailingSlashPrerender
+ ? 'with trailing slash'
+ : 'without trailing slash'
+ : '',
+)
+
/**
* See https://playwright.dev/docs/test-configuration.
*/
@@ -27,10 +52,20 @@ export default defineConfig({
},
webServer: {
- command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} PORT=${PORT} pnpm start`,
+ command: getCommand(),
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
+ env: {
+ MODE: process.env.MODE || '',
+ TRAILING_SLASH: isTrailingSlashPrerender.toString(),
+ VITE_TRAILING_SLASH: isTrailingSlashPrerender.toString(),
+ VITE_NODE_ENV: 'test',
+ VITE_EXTERNAL_PORT: String(EXTERNAL_PORT),
+ VITE_SERVER_PORT: String(PORT),
+ START_PORT: String(START_PORT),
+ PORT: String(PORT),
+ },
},
projects: [
diff --git a/e2e/solid-start/custom-basepath/src/router.tsx b/e2e/solid-start/custom-basepath/src/router.tsx
index b940fffea80..71efa7e12a1 100644
--- a/e2e/solid-start/custom-basepath/src/router.tsx
+++ b/e2e/solid-start/custom-basepath/src/router.tsx
@@ -2,7 +2,6 @@ import { createRouter } from '@tanstack/solid-router'
import { routeTree } from './routeTree.gen'
import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
import { NotFound } from './components/NotFound'
-import { basepath } from './utils/basepath'
export function getRouter() {
const router = createRouter({
@@ -11,7 +10,7 @@ export function getRouter() {
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: () => ,
scrollRestoration: true,
- basepath: basepath,
+ basepath: import.meta.env.BASE_URL,
})
return router
diff --git a/e2e/solid-start/custom-basepath/src/routes/users.$userId.tsx b/e2e/solid-start/custom-basepath/src/routes/users.$userId.tsx
index db5614394af..29eeb3ad641 100644
--- a/e2e/solid-start/custom-basepath/src/routes/users.$userId.tsx
+++ b/e2e/solid-start/custom-basepath/src/routes/users.$userId.tsx
@@ -1,15 +1,17 @@
import { createFileRoute } from '@tanstack/solid-router'
import axios from 'redaxios'
-
+import { getRouterInstance } from '@tanstack/solid-start'
import type { User } from '~/utils/users'
import { NotFound } from '~/components/NotFound'
import { UserErrorComponent } from '~/components/UserErrorComponent'
-import { basepath } from '~/utils/basepath'
export const Route = createFileRoute('/users/$userId')({
loader: async ({ params: { userId } }) => {
+ const router = await getRouterInstance()
return await axios
- .get(basepath + '/api/users/' + userId)
+ .get(`/${router.options.basepath}/api/users/${userId}`, {
+ baseURL: router.origin,
+ })
.then((r) => r.data)
.catch(() => {
throw new Error('Failed to fetch user')
diff --git a/e2e/solid-start/custom-basepath/src/routes/users.tsx b/e2e/solid-start/custom-basepath/src/routes/users.tsx
index b686fa91cda..ee0fcda038f 100644
--- a/e2e/solid-start/custom-basepath/src/routes/users.tsx
+++ b/e2e/solid-start/custom-basepath/src/routes/users.tsx
@@ -1,12 +1,15 @@
import { Link, Outlet, createFileRoute } from '@tanstack/solid-router'
import axios from 'redaxios'
+import { getRouterInstance } from '@tanstack/solid-start'
import type { User } from '~/utils/users'
-import { basepath } from '~/utils/basepath'
export const Route = createFileRoute('/users')({
loader: async () => {
+ const router = await getRouterInstance()
return await axios
- .get>(basepath + '/api/users')
+ .get>(`/${router.options.basepath}/api/users`, {
+ baseURL: router.origin,
+ })
.then((r) => r.data)
.catch(() => {
throw new Error('Failed to fetch users')
diff --git a/e2e/solid-start/custom-basepath/src/utils/basepath.ts b/e2e/solid-start/custom-basepath/src/utils/basepath.ts
deleted file mode 100644
index 6e719f196cf..00000000000
--- a/e2e/solid-start/custom-basepath/src/utils/basepath.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const basepath = '/custom/basepath'
diff --git a/e2e/solid-start/custom-basepath/tests/navigation.spec.ts b/e2e/solid-start/custom-basepath/tests/navigation.spec.ts
index 3e6a5bd1bea..fba5af53848 100644
--- a/e2e/solid-start/custom-basepath/tests/navigation.spec.ts
+++ b/e2e/solid-start/custom-basepath/tests/navigation.spec.ts
@@ -66,7 +66,7 @@ test('server-side redirect', async ({ page, baseURL }) => {
.get('/redirect/throw-it', { maxRedirects: 0 })
.then((res) => {
const headers = new Headers(res.headers())
- expect(headers.get('location')).toBe('/custom/basepath/redirect/throw-it')
+ expect(headers.get('location')).toBe('/custom/basepath/posts/1')
})
await page.request
.get('/custom/basepath/redirect/throw-it', { maxRedirects: 0 })
diff --git a/e2e/solid-start/custom-basepath/tests/utils/isPrerender.ts b/e2e/solid-start/custom-basepath/tests/utils/isPrerender.ts
new file mode 100644
index 00000000000..d5d991d4545
--- /dev/null
+++ b/e2e/solid-start/custom-basepath/tests/utils/isPrerender.ts
@@ -0,0 +1 @@
+export const isPrerender: boolean = process.env.MODE === 'prerender'
diff --git a/e2e/solid-start/custom-basepath/vite.config.ts b/e2e/solid-start/custom-basepath/vite.config.ts
index bbc8fea1019..d35cd033419 100644
--- a/e2e/solid-start/custom-basepath/vite.config.ts
+++ b/e2e/solid-start/custom-basepath/vite.config.ts
@@ -3,9 +3,26 @@ import tsConfigPaths from 'vite-tsconfig-paths'
import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
import viteSolid from 'vite-plugin-solid'
import tailwindcss from '@tailwindcss/vite'
+import { isPrerender } from './tests/utils/isPrerender'
+
+const prerenderConfiguration = {
+ enabled: true,
+ filter: (page: { path: string }) => {
+ return ![
+ '/i-do-not-exist',
+ '/posts',
+ '/redirect',
+ '/this-route-does-not-exist',
+ '/users',
+ ].some((p) => page.path.includes(p))
+ },
+ onSuccess: ({ page }: { page: { path: string } }) => {
+ console.log(`Rendered ${page.path}!`)
+ },
+}
export default defineConfig({
- base: '/custom/basepath',
+ base: `/custom/basepath${process.env.TRAILING_SLASH?.toLowerCase() === 'true' ? '/' : ''}`,
server: {
port: 3000,
},
@@ -14,7 +31,9 @@ export default defineConfig({
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
- tanstackStart(),
+ tanstackStart({
+ prerender: isPrerender ? prerenderConfiguration : undefined,
+ }),
viteSolid({ ssr: true }),
],
})
diff --git a/e2e/vue-start/custom-basepath/package.json b/e2e/vue-start/custom-basepath/package.json
index a4b30785ab3..d93af7e9f1b 100644
--- a/e2e/vue-start/custom-basepath/package.json
+++ b/e2e/vue-start/custom-basepath/package.json
@@ -6,9 +6,16 @@
"scripts": {
"dev": "cross-env NODE_ENV=development tsx express-server.ts",
"build": "vite build && tsc --noEmit",
+ "build:prerender": "cross-env MODE=prerender vite build && tsc --noEmit",
+ "build:prerender:trailing": "cross-env MODE=prerender TRAILING_SLASH=true vite build && tsc --noEmit",
"preview": "vite preview",
"start": "tsx express-server.ts",
- "test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
+ "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' &",
+ "test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'",
+ "test:e2e:prerender": "rm -rf port*.txt; cross-env MODE=prerender playwright test --project=chromium",
+ "test:e2e:prerender:trailing": "rm -rf port*.txt; cross-env MODE=prerender TRAILING_SLASH=true playwright test --project=chromium",
+ "test:e2e:ssrMode": "rm -rf port*.txt; playwright test --project=chromium",
+ "test:e2e": "pnpm run test:e2e:ssrMode && pnpm run test:e2e:prerender && pnpm run test:e2e:prerender:trailing"
},
"dependencies": {
"@tanstack/vue-router": "workspace:^",
diff --git a/e2e/vue-start/custom-basepath/playwright.config.ts b/e2e/vue-start/custom-basepath/playwright.config.ts
index 5095425f473..17c013aaa6c 100644
--- a/e2e/vue-start/custom-basepath/playwright.config.ts
+++ b/e2e/vue-start/custom-basepath/playwright.config.ts
@@ -4,11 +4,36 @@ import {
getTestServerPort,
} from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }
+import { isPrerender } from './tests/utils/isPrerender'
const PORT = await getTestServerPort(packageJson.name)
+const START_PORT = await getTestServerPort(packageJson.name)
const EXTERNAL_PORT = await getDummyServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}/custom/basepath`
+const ssrModeCommand = `pnpm build && pnpm start`
+const prerenderModeCommand = `pnpm run test:e2e:startDummyServer && pnpm build:prerender && pnpm run test:e2e:stopDummyServer && pnpm start`
+const prerenderTrailingModeCommand = `pnpm run test:e2e:startDummyServer && pnpm build:prerender:trailing && pnpm run test:e2e:stopDummyServer && pnpm start`
+const isTrailingSlashPrerender =
+ process.env.TRAILING_SLASH?.toLowerCase() === 'true'
+
+const getCommand = () => {
+ if (isPrerender && isTrailingSlashPrerender)
+ return prerenderTrailingModeCommand
+ if (isPrerender) return prerenderModeCommand
+ return ssrModeCommand
+}
+
+console.log(
+ 'running in prerender mode: ',
+ isPrerender.toString(),
+ isPrerender
+ ? isTrailingSlashPrerender
+ ? 'with trailing slash'
+ : 'without trailing slash'
+ : '',
+)
+
/**
* See https://playwright.dev/docs/test-configuration.
*/
@@ -27,10 +52,20 @@ export default defineConfig({
},
webServer: {
- command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} PORT=${PORT} pnpm start`,
+ command: getCommand(),
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
+ env: {
+ MODE: process.env.MODE || '',
+ TRAILING_SLASH: isTrailingSlashPrerender.toString(),
+ VITE_TRAILING_SLASH: isTrailingSlashPrerender.toString(),
+ VITE_NODE_ENV: 'test',
+ VITE_EXTERNAL_PORT: String(EXTERNAL_PORT),
+ VITE_SERVER_PORT: String(PORT),
+ START_PORT: String(START_PORT),
+ PORT: String(PORT),
+ },
},
projects: [
diff --git a/e2e/vue-start/custom-basepath/src/router.tsx b/e2e/vue-start/custom-basepath/src/router.tsx
index 81103bf0bd6..33ff448cb86 100644
--- a/e2e/vue-start/custom-basepath/src/router.tsx
+++ b/e2e/vue-start/custom-basepath/src/router.tsx
@@ -2,7 +2,6 @@ import { createRouter } from '@tanstack/vue-router'
import { routeTree } from './routeTree.gen'
import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
import { NotFound } from './components/NotFound'
-import { basepath } from './utils/basepath'
export function getRouter() {
const router = createRouter({
@@ -11,7 +10,7 @@ export function getRouter() {
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: () => ,
scrollRestoration: true,
- basepath: basepath,
+ basepath: import.meta.env.BASE_URL,
})
return router
diff --git a/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx b/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
index fb7fce5f4c4..e3f84b79c9a 100644
--- a/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/users.$userId.tsx
@@ -1,15 +1,17 @@
import { createFileRoute } from '@tanstack/vue-router'
+import { getRouterInstance } from '@tanstack/vue-start'
import axios from 'redaxios'
-
import type { User } from '~/utils/users'
import { NotFound } from '~/components/NotFound'
import { UserErrorComponent } from '~/components/UserErrorComponent'
-import { basepath } from '~/utils/basepath'
export const Route = createFileRoute('/users/$userId')({
loader: async ({ params: { userId } }) => {
+ const router = await getRouterInstance()
return await axios
- .get(basepath + '/api/users/' + userId)
+ .get(`/${router.options.basepath}/api/users/${userId}`, {
+ baseURL: router.origin,
+ })
.then((r) => r.data)
.catch(() => {
throw new Error('Failed to fetch user')
diff --git a/e2e/vue-start/custom-basepath/src/routes/users.tsx b/e2e/vue-start/custom-basepath/src/routes/users.tsx
index 1432e504516..09c11dad56e 100644
--- a/e2e/vue-start/custom-basepath/src/routes/users.tsx
+++ b/e2e/vue-start/custom-basepath/src/routes/users.tsx
@@ -1,12 +1,15 @@
import { Link, Outlet, createFileRoute } from '@tanstack/vue-router'
+import { getRouterInstance } from '@tanstack/vue-start'
import axios from 'redaxios'
import type { User } from '~/utils/users'
-import { basepath } from '~/utils/basepath'
export const Route = createFileRoute('/users')({
loader: async () => {
+ const router = await getRouterInstance()
return await axios
- .get>(basepath + '/api/users')
+ .get>(`/${router.options.basepath}/api/users`, {
+ baseURL: router.origin,
+ })
.then((r) => r.data)
.catch(() => {
throw new Error('Failed to fetch users')
diff --git a/e2e/vue-start/custom-basepath/src/utils/basepath.ts b/e2e/vue-start/custom-basepath/src/utils/basepath.ts
deleted file mode 100644
index 6e719f196cf..00000000000
--- a/e2e/vue-start/custom-basepath/src/utils/basepath.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const basepath = '/custom/basepath'
diff --git a/e2e/vue-start/custom-basepath/tests/navigation.spec.ts b/e2e/vue-start/custom-basepath/tests/navigation.spec.ts
index 3e6a5bd1bea..fba5af53848 100644
--- a/e2e/vue-start/custom-basepath/tests/navigation.spec.ts
+++ b/e2e/vue-start/custom-basepath/tests/navigation.spec.ts
@@ -66,7 +66,7 @@ test('server-side redirect', async ({ page, baseURL }) => {
.get('/redirect/throw-it', { maxRedirects: 0 })
.then((res) => {
const headers = new Headers(res.headers())
- expect(headers.get('location')).toBe('/custom/basepath/redirect/throw-it')
+ expect(headers.get('location')).toBe('/custom/basepath/posts/1')
})
await page.request
.get('/custom/basepath/redirect/throw-it', { maxRedirects: 0 })
diff --git a/e2e/vue-start/custom-basepath/tests/utils/isPrerender.ts b/e2e/vue-start/custom-basepath/tests/utils/isPrerender.ts
new file mode 100644
index 00000000000..d5d991d4545
--- /dev/null
+++ b/e2e/vue-start/custom-basepath/tests/utils/isPrerender.ts
@@ -0,0 +1 @@
+export const isPrerender: boolean = process.env.MODE === 'prerender'
diff --git a/e2e/vue-start/custom-basepath/vite.config.ts b/e2e/vue-start/custom-basepath/vite.config.ts
index 4d6e769fe88..5ed7688f7f3 100644
--- a/e2e/vue-start/custom-basepath/vite.config.ts
+++ b/e2e/vue-start/custom-basepath/vite.config.ts
@@ -4,9 +4,26 @@ import { tanstackStart } from '@tanstack/vue-start/plugin/vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import tailwindcss from '@tailwindcss/vite'
+import { isPrerender } from './tests/utils/isPrerender'
+
+const prerenderConfiguration = {
+ enabled: true,
+ filter: (page: { path: string }) => {
+ return ![
+ '/i-do-not-exist',
+ '/posts',
+ '/redirect',
+ '/this-route-does-not-exist',
+ '/users',
+ ].some((p) => page.path.includes(p))
+ },
+ onSuccess: ({ page }: { page: { path: string } }) => {
+ console.log(`Rendered ${page.path}!`)
+ },
+}
export default defineConfig({
- base: '/custom/basepath',
+ base: `/custom/basepath${process.env.TRAILING_SLASH?.toLowerCase() === 'true' ? '/' : ''}`,
server: {
port: 3000,
},
@@ -15,7 +32,9 @@ export default defineConfig({
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
- tanstackStart(),
+ tanstackStart({
+ prerender: isPrerender ? prerenderConfiguration : undefined,
+ }),
vue(),
vueJsx(),
],
diff --git a/packages/start-plugin-core/src/prerender.ts b/packages/start-plugin-core/src/prerender.ts
index ea6368109e5..d55af441eea 100644
--- a/packages/start-plugin-core/src/prerender.ts
+++ b/packages/start-plugin-core/src/prerender.ts
@@ -2,6 +2,7 @@ import { promises as fsp } from 'node:fs'
import os from 'node:os'
import path from 'pathe'
import { joinURL, withBase, withoutBase } from 'ufo'
+import { removeTrailingSlash } from '@tanstack/router-generator'
import { VITE_ENVIRONMENT_NAMES } from './constants'
import { createLogger } from './utils'
import { Queue } from './queue'
@@ -270,6 +271,7 @@ async function startPreviewServer(
try {
return await vite.preview({
configFile: viteConfig.configFile,
+ base: removeTrailingSlash(viteConfig.base),
preview: {
port: 0,
open: false,
diff --git a/packages/start-server-core/src/createStartHandler.ts b/packages/start-server-core/src/createStartHandler.ts
index 1f2820c7f13..4649d324dc2 100644
--- a/packages/start-server-core/src/createStartHandler.ts
+++ b/packages/start-server-core/src/createStartHandler.ts
@@ -9,6 +9,7 @@ import {
executeRewriteInput,
isRedirect,
isResolvedRedirect,
+ joinPaths,
} from '@tanstack/router-core'
import {
attachRouterServerSsrUtils,
@@ -210,6 +211,13 @@ export function createStartHandler(
try {
const url = new URL(request.url)
+ const basePath = joinPaths(['/', ROUTER_BASEPATH])
+
+ if (!url.pathname.startsWith(basePath)) {
+ url.pathname = joinPaths([basePath, url.pathname])
+ request = new Request(url, request)
+ }
+
const href = url.href.replace(url.origin, '')
const origin = getOrigin(request)