diff --git a/packages/void/src/auth/drizzle.ts b/packages/void/src/auth/drizzle.ts index 5ffd2fd4f..6cbbb60e5 100644 --- a/packages/void/src/auth/drizzle.ts +++ b/packages/void/src/auth/drizzle.ts @@ -91,7 +91,7 @@ function resolveSchemaUserConfig( }); } - return userConfig; + return { ...defaults, ...userConfig }; } function resolveSchemaOptions( diff --git a/packages/void/src/plugin-routing.ts b/packages/void/src/plugin-routing.ts index a9c582639..2cd8eeb6d 100644 --- a/packages/void/src/plugin-routing.ts +++ b/packages/void/src/plugin-routing.ts @@ -246,6 +246,23 @@ export function routingPlugin(root: string, options: RoutingPluginOptions): Plug ); }, config(_, env) { + if (env.command === "serve" && options.isAuthEnabled()) { + // Alias better-auth client subpaths to absolute file paths so + // Vite can resolve them during SSR without the bare specifiers + // needing to be hoisted into the user's node_modules (pnpm + // strict mode). Without this, Vite discovers them lazily on + // first auth page load, triggering a disruptive program reload. + const betterAuthSubpaths = ["client", "react", "vue", "svelte", "solid"]; + const resolvedAliases = betterAuthSubpaths.flatMap((sub) => { + try { + return [{ find: `better-auth/${sub}`, replacement: resolveFile(`better-auth/${sub}`) }]; + } catch { + return []; + } + }); + return { resolve: { alias: resolvedAliases } }; + } + const entries = validateSsrEntries(root); if (!entries || env.command !== "build") return; diff --git a/packages/void/src/runtime/better-auth-shared.ts b/packages/void/src/runtime/better-auth-shared.ts index 9ab9070ee..4774a09dc 100644 --- a/packages/void/src/runtime/better-auth-shared.ts +++ b/packages/void/src/runtime/better-auth-shared.ts @@ -179,7 +179,7 @@ function resolveUserConfig( return userConfig({ defaults, request, env, dialect }); } - return userConfig; + return { ...defaults, ...userConfig }; } function assertRuntimeDialect(runtime: VoidAuthRuntimeOptions, dialect: VoidAuthDialect): void { diff --git a/packages/void/test/integration/kitchen-sink.test.ts b/packages/void/test/integration/kitchen-sink.test.ts index 1207ae599..073e2777b 100644 --- a/packages/void/test/integration/kitchen-sink.test.ts +++ b/packages/void/test/integration/kitchen-sink.test.ts @@ -64,7 +64,13 @@ describe("playground/kitchen-sink", () => { }); await waitForServer(BASE, 20_000); - }, 30_000); + + // Warm up: the first request triggers Vite SSR dep optimization + // (drizzle-orm, vue, hono, etc.) which causes multiple program + // reloads. Wait for the reloads to settle before running tests. + await fetch(BASE).catch(() => {}); + await new Promise((resolve) => setTimeout(resolve, 5_000)); + }, 40_000); afterAll(async () => { if (child) await killChild(child); diff --git a/packages/void/test/integration/projects-command.test.ts b/packages/void/test/integration/projects-command.test.ts index 5c2ff63db..89b29fd01 100644 --- a/packages/void/test/integration/projects-command.test.ts +++ b/packages/void/test/integration/projects-command.test.ts @@ -88,9 +88,9 @@ describe("runProjectsCommand", () => { headers: { Authorization: "Bearer test-token" }, }); expect(out).toContain("star-tracker"); - expect(out).toContain("https://star-tracker.void.app"); + expect(out).toContain("https://star-tracker.staging.void.app"); expect(out).toContain("todo-app"); - expect(out).toContain("https://todo-app.void.app"); + expect(out).toContain("https://todo-app.staging.void.app"); }); it("prints a friendly message when there are no projects", async () => {