From cb8197bfa56a28e6d613249ca759dc94380b4cde Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Thu, 29 Oct 2026 22:14:03 +0000 Subject: [PATCH] fix(start-plugin-core): resolve prerender paths against the Vite root prerenderWithVite resolved the client output directory, the preview server and the server build entry against process.cwd(), so `vite build ` and `createBuilder({ root })` failed to start the preview server or wrote index.html, pages.json and sitemap.xml outside the project whenever the Vite root was not the working directory. Resolve all three against the resolved config root and pass root to vite.preview(). --- .changeset/quiet-prerender-root.md | 5 ++++ .../src/vite/post-server-build.ts | 3 ++- .../start-plugin-core/src/vite/prerender.ts | 9 ++++++- .../src/vite/preview-server-plugin/plugin.ts | 7 +++-- .../tests/prerender-vite.test.ts | 27 +++++++++++++++++++ 5 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 .changeset/quiet-prerender-root.md 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') + }) })