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)