diff --git a/.changeset/quiet-prerender-root.md b/.changeset/quiet-prerender-root.md
new file mode 100644
index 00000000000..c178e6cd905
--- /dev/null
+++ b/.changeset/quiet-prerender-root.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/start-plugin-core': patch
+---
+
+Resolve the prerender paths against Vite's resolved `root` instead of `process.cwd()`. When the root is not the working directory, `vite build
` and `createBuilder({ root })` previously failed to start the preview server or wrote `index.html`, `pages.json` and `sitemap.xml` outside the project.
diff --git a/packages/start-plugin-core/src/vite/post-server-build.ts b/packages/start-plugin-core/src/vite/post-server-build.ts
index 2e74b7635d1..5ad1b40445e 100644
--- a/packages/start-plugin-core/src/vite/post-server-build.ts
+++ b/packages/start-plugin-core/src/vite/post-server-build.ts
@@ -1,3 +1,4 @@
+import { resolve } from 'pathe'
import { postBuild } from '../post-build'
import { getClientOutputDirectory } from './output-directory'
import { prerenderWithVite } from './prerender'
@@ -15,7 +16,7 @@ export async function postServerBuild({
startConfig,
adapter: {
getClientOutputDirectory() {
- return getClientOutputDirectory(builder.config)
+ return resolve(builder.config.root, getClientOutputDirectory(builder.config))
},
prerender(startConfig) {
return prerenderWithVite({
diff --git a/packages/start-plugin-core/src/vite/prerender.ts b/packages/start-plugin-core/src/vite/prerender.ts
index 5d1838e43aa..7d2f0eafbca 100644
--- a/packages/start-plugin-core/src/vite/prerender.ts
+++ b/packages/start-plugin-core/src/vite/prerender.ts
@@ -1,3 +1,4 @@
+import { resolve } from 'pathe'
import { VITE_ENVIRONMENT_NAMES } from '../constants'
import { prerender } from '../prerender'
import type { PrerenderHandler } from '../prerender'
@@ -26,7 +27,10 @@ export async function prerenderWithVite({
)
}
- const outputDir = clientEnv.config.build.outDir
+ const outputDir = resolve(
+ clientEnv.config.root,
+ clientEnv.config.build.outDir,
+ )
process.env.TSS_PRERENDERING = 'true'
process.env.TSS_CLIENT_OUTPUT_DIR = outputDir
@@ -66,6 +70,9 @@ async function startPreviewServer(
try {
return await vite.preview({
+ // Reuse the root the build resolved, so the preview config is not
+ // re-resolved against `process.cwd()` when the root is not the cwd.
+ root: viteConfig.root,
configFile: viteConfig.configFile,
preview: {
port: 0,
diff --git a/packages/start-plugin-core/src/vite/preview-server-plugin/plugin.ts b/packages/start-plugin-core/src/vite/preview-server-plugin/plugin.ts
index 1541d9edfbe..d9c815b855b 100644
--- a/packages/start-plugin-core/src/vite/preview-server-plugin/plugin.ts
+++ b/packages/start-plugin-core/src/vite/preview-server-plugin/plugin.ts
@@ -1,5 +1,5 @@
import { pathToFileURL } from 'node:url'
-import { basename, extname, join } from 'pathe'
+import { basename, extname, join, resolve } from 'pathe'
import { NodeRequest, sendNodeResponse } from 'srvx/node'
import { joinURL } from 'ufo'
import { VITE_ENVIRONMENT_NAMES } from '../../constants'
@@ -36,7 +36,10 @@ export function previewServerPlugin(): Plugin {
// Get basename without extension and add .js
const outputFilename = `${basename(serverInput, extname(serverInput))}.js`
- const serverOutputDir = getServerOutputDirectory(server.config)
+ const serverOutputDir = resolve(
+ server.config.root,
+ getServerOutputDirectory(server.config),
+ )
const serverEntryPath = join(serverOutputDir, outputFilename)
const imported = await import(
pathToFileURL(serverEntryPath).toString()
diff --git a/packages/start-plugin-core/tests/prerender-vite.test.ts b/packages/start-plugin-core/tests/prerender-vite.test.ts
index 013557e4dfa..12fb0fc12fe 100644
--- a/packages/start-plugin-core/tests/prerender-vite.test.ts
+++ b/packages/start-plugin-core/tests/prerender-vite.test.ts
@@ -122,4 +122,31 @@ describe('Vite prerender network sink', () => {
'http://127.0.0.1:4173/next/?from=about',
])
})
+
+ it('resolves the client output directory and the preview root against the Vite root', async () => {
+ vi.stubGlobal(
+ 'fetch',
+ vi.fn(async () => new Response(null, { status: 200 })),
+ )
+ const builder = {
+ environments: {
+ ssr: {
+ config: { root: '/repo/project', configFile: '/vite.config.ts' },
+ },
+ client: {
+ config: { root: '/repo/project', build: { outDir: 'dist/client' } },
+ },
+ },
+ } as any
+
+ await prerenderWithVite({
+ startConfig: { ...makeStartConfig(), pages: [] },
+ builder,
+ })
+
+ expect(preview).toHaveBeenCalledWith(
+ expect.objectContaining({ root: '/repo/project' }),
+ )
+ expect(process.env.TSS_CLIENT_OUTPUT_DIR).toBe('/repo/project/dist/client')
+ })
})