@better-t-stack/template-generator 3.35.2 → 3.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -189,6 +189,33 @@ Handlebars.registerHelper("and", (...args) => args.slice(0, -1).every(Boolean));
189
189
  Handlebars.registerHelper("or", (...args) => args.slice(0, -1).some(Boolean));
190
190
  Handlebars.registerHelper("not", (a) => !a);
191
191
  Handlebars.registerHelper("includes", (arr, val) => Array.isArray(arr) && arr.includes(val));
192
+ const getServerUrlSource = `function getServerUrl(url: string) {
193
+ const normalized = url.endsWith("/") ? url.slice(0, -1) : url;
194
+
195
+ if (!normalized.startsWith("/")) {
196
+ return normalized;
197
+ }
198
+
199
+ if (typeof window !== "undefined") {
200
+ return \`\${window.location.origin}\${normalized}\`;
201
+ }
202
+
203
+ const processEnv = (globalThis as {
204
+ process?: { env?: Record<string, string | undefined> };
205
+ }).process?.env;
206
+ const vercelUrl =
207
+ processEnv?.VERCEL_ENV === "production"
208
+ ? (processEnv?.VERCEL_PROJECT_PRODUCTION_URL ?? processEnv?.VERCEL_URL)
209
+ : (processEnv?.VERCEL_URL ?? processEnv?.VERCEL_PROJECT_PRODUCTION_URL);
210
+ if (vercelUrl) {
211
+ const origin = vercelUrl.startsWith("http") ? vercelUrl : \`https://\${vercelUrl}\`;
212
+ return \`\${origin}\${normalized}\`;
213
+ }
214
+
215
+ return \`http://localhost:3000\${normalized}\`;
216
+ }`;
217
+ Handlebars.registerPartial("getServerUrl", getServerUrlSource);
218
+ Handlebars.registerPartial("getServerUrlSpaces", getServerUrlSource.replaceAll(" ", " "));
192
219
  function processTemplateString(content, context) {
193
220
  return Handlebars.compile(content)(context);
194
221
  }
@@ -201,6 +228,7 @@ function transformFilename(filename) {
201
228
  if (basename === "_gitignore") result = result.replace(/_gitignore$/, ".gitignore");
202
229
  else if (basename === "_npmrc") result = result.replace(/_npmrc$/, ".npmrc");
203
230
  else if (basename === "_dockerignore") result = result.replace(/_dockerignore$/, ".dockerignore");
231
+ else if (basename === "_vercelignore") result = result.replace(/_vercelignore$/, ".vercelignore");
204
232
  return result;
205
233
  }
206
234
  function processFileContent(filePath, content, context) {
@@ -385,27 +413,27 @@ function updatePackageJsonsWithCatalogs(vfs, packagesInfo, catalog) {
385
413
  //#region src/utils/add-deps.ts
386
414
  const dependencyVersionMap = {
387
415
  typescript: "^6",
388
- "better-auth": "1.6.22",
389
- "@better-auth/expo": "1.6.22",
390
- "@clerk/backend": "^3.2.1",
391
- "@clerk/express": "^2.0.5",
392
- "@clerk/fastify": "^3.1.3",
393
- "@clerk/nextjs": "^7.0.5",
394
- "@clerk/react": "^6.1.1",
395
- "@clerk/react-router": "^3.0.5",
396
- "@clerk/tanstack-react-start": "^1.1.3",
397
- "@clerk/expo": "^3.1.3",
398
- "drizzle-orm": "^0.45.1",
399
- "drizzle-kit": "^0.31.8",
400
- "@planetscale/database": "^1.19.0",
401
- "@libsql/client": "0.15.15",
402
- libsql: "0.5.22",
403
- "@neondatabase/serverless": "^1.0.2",
404
- pg: "^8.17.1",
405
- "@types/pg": "^8.16.0",
416
+ "better-auth": "1.6.23",
417
+ "@better-auth/expo": "1.6.23",
418
+ "@clerk/backend": "^3.10.0",
419
+ "@clerk/express": "^2.1.35",
420
+ "@clerk/fastify": "^3.1.45",
421
+ "@clerk/nextjs": "^7.5.12",
422
+ "@clerk/react": "^6.11.3",
423
+ "@clerk/react-router": "^3.5.4",
424
+ "@clerk/tanstack-react-start": "^1.4.12",
425
+ "@clerk/expo": "^3.6.5",
426
+ "drizzle-orm": "^0.45.2",
427
+ "drizzle-kit": "^0.31.10",
428
+ "@planetscale/database": "^1.20.1",
429
+ "@libsql/client": "0.17.4",
430
+ libsql: "0.5.29",
431
+ "@neondatabase/serverless": "^1.1.0",
432
+ pg: "^8.22.0",
433
+ "@types/pg": "^8.20.0",
406
434
  "@types/ws": "^8.18.1",
407
- ws: "^8.18.3",
408
- mysql2: "^3.14.0",
435
+ ws: "^8.21.0",
436
+ mysql2: "^3.22.5",
409
437
  "@prisma/client": "^7.8.0",
410
438
  prisma: "^7.8.0",
411
439
  "@prisma/adapter-d1": "^7.8.0",
@@ -415,57 +443,57 @@ const dependencyVersionMap = {
415
443
  "@prisma/adapter-better-sqlite3": "^7.8.0",
416
444
  "@prisma/adapter-pg": "^7.8.0",
417
445
  "@prisma/adapter-planetscale": "^7.8.0",
418
- mongoose: "^9.6.2",
419
- mongodb: "^7.2.0",
446
+ mongoose: "^9.7.3",
447
+ mongodb: "^7.4.0",
420
448
  "vite-plugin-pwa": "^1.3.0",
421
449
  "@vite-pwa/assets-generator": "^1.0.2",
422
- "@tauri-apps/cli": "^2.11.2",
423
- "@biomejs/biome": "^2.4.16",
424
- oxlint: "^1.68.0",
425
- oxfmt: "^0.53.0",
450
+ "@tauri-apps/cli": "^2.11.4",
451
+ "@biomejs/biome": "^2.5.2",
452
+ oxlint: "^1.72.0",
453
+ oxfmt: "^0.57.0",
426
454
  husky: "^9.1.7",
427
455
  lefthook: "^2.1.9",
428
456
  "lint-staged": "^17.0.7",
429
- tsx: "^4.19.2",
457
+ tsx: "^4.22.5",
430
458
  "@types/node": "^22.13.14",
431
- "@types/bun": "^1.3.4",
459
+ "@types/bun": "^1.3.14",
432
460
  "@elysiajs/node": "^1.4.5",
433
- "@elysiajs/cors": "^1.4.1",
461
+ "@elysiajs/cors": "^1.4.2",
434
462
  "@elysiajs/trpc": "^1.1.0",
435
- elysia: "^1.4.28",
463
+ elysia: "^1.4.29",
436
464
  "@sinclair/typebox": "^0.34.49",
437
- "@hono/node-server": "^1.14.4",
438
- "@hono/trpc-server": "^0.4.0",
439
- hono: "^4.8.2",
440
- cors: "^2.8.5",
441
- express: "^5.1.0",
442
- "@types/express": "^5.0.1",
443
- "@types/cors": "^2.8.17",
444
- fastify: "^5.3.3",
445
- "@fastify/cors": "^11.0.1",
465
+ "@hono/node-server": "^2.0.8",
466
+ "@hono/trpc-server": "^0.4.2",
467
+ hono: "^4.12.27",
468
+ cors: "^2.8.6",
469
+ express: "^5.2.1",
470
+ "@types/express": "^5.0.6",
471
+ "@types/cors": "^2.8.19",
472
+ fastify: "^5.9.0",
473
+ "@fastify/cors": "^11.2.0",
446
474
  turbo: "^2.10.2",
447
- nx: "^22.7.5",
448
- "vite-plus": "0.1.24",
449
- rolldown: "1.1.0",
450
- ai: "^7.0.3",
451
- "@ai-sdk/google": "^4.0.1",
452
- "@ai-sdk/vue": "^4.0.3",
453
- "@ai-sdk/svelte": "^5.0.3",
454
- "@ai-sdk/react": "^4.0.4",
455
- "@ai-sdk/devtools": "^1.0.0",
475
+ nx: "^23.0.1",
476
+ "vite-plus": "0.2.2",
477
+ rolldown: "1.1.4",
478
+ ai: "^7.0.13",
479
+ "@ai-sdk/google": "^4.0.7",
480
+ "@ai-sdk/vue": "^4.0.13",
481
+ "@ai-sdk/svelte": "^5.0.13",
482
+ "@ai-sdk/react": "^4.0.14",
483
+ "@ai-sdk/devtools": "^1.0.1",
456
484
  streamdown: "^2.5.0",
457
- shiki: "^3.20.0",
485
+ shiki: "^4.3.0",
458
486
  "@orpc/server": "^1.14.6",
459
487
  "@orpc/client": "^1.14.6",
460
488
  "@orpc/openapi": "^1.14.6",
461
489
  "@orpc/zod": "^1.14.6",
462
490
  "@orpc/tanstack-query": "^1.14.6",
463
- "@trpc/tanstack-react-query": "^11.16.0",
464
- "@trpc/server": "^11.16.0",
465
- "@trpc/client": "^11.16.0",
491
+ "@trpc/tanstack-react-query": "^11.18.0",
492
+ "@trpc/server": "^11.18.0",
493
+ "@trpc/client": "^11.18.0",
466
494
  next: "^16.2.0",
467
- nitro: "^3.0.260429-beta",
468
- convex: "^1.42.0",
495
+ nitro: "^3.0.260610-beta",
496
+ convex: "^1.42.1",
469
497
  "@convex-dev/react-query": "^0.1.0",
470
498
  "@convex-dev/agent": "^0.6.4",
471
499
  "@convex-dev/polar": "^0.9.2",
@@ -473,40 +501,43 @@ const dependencyVersionMap = {
473
501
  "convex-nuxt": "0.1.5",
474
502
  "convex-vue": "^0.1.5",
475
503
  "@convex-dev/better-auth": "^0.12.5",
476
- "@tanstack/svelte-query": "^5.85.3",
477
- "@tanstack/svelte-query-devtools": "^5.85.3",
478
- "@tanstack/vue-query-devtools": "^6.1.5",
479
- "@tanstack/vue-query": "^5.92.9",
480
- "@tanstack/react-query-devtools": "^5.91.1",
481
- "@tanstack/react-query": "^5.90.12",
482
- "@tanstack/react-form": "^1.28.0",
483
- "@tanstack/react-router-ssr-query": "^1.166.11",
484
- "@tanstack/solid-form": "^1.28.0",
485
- "@tanstack/svelte-form": "^1.28.0",
486
- "@tanstack/solid-query": "^5.99.1",
487
- "@tanstack/solid-query-devtools": "^5.99.1",
488
- "@tanstack/solid-router-devtools": "^1.166.13",
489
- wrangler: "^4.103.0",
490
- "@cloudflare/vite-plugin": "^1.17.1",
491
- "@opennextjs/cloudflare": "^1.17.3",
504
+ "@tanstack/svelte-query": "^6.1.36",
505
+ "@tanstack/svelte-query-devtools": "^6.1.36",
506
+ "@tanstack/vue-query-devtools": "^6.1.36",
507
+ "@tanstack/vue-query": "^5.101.2",
508
+ "@tanstack/react-query-devtools": "^5.101.2",
509
+ "@tanstack/react-query": "^5.101.2",
510
+ "@tanstack/react-form": "^1.33.0",
511
+ "@tanstack/react-router-ssr-query": "^1.167.1",
512
+ "@tanstack/solid-form": "^1.33.0",
513
+ "@tanstack/svelte-form": "^1.33.0",
514
+ "@tanstack/solid-query": "^5.101.2",
515
+ "@tanstack/solid-query-devtools": "^5.101.2",
516
+ "@tanstack/solid-router-devtools": "^1.167.0",
517
+ wrangler: "^4.107.0",
518
+ "@cloudflare/vite-plugin": "^1.43.0",
519
+ "@opennextjs/cloudflare": "^1.20.1",
492
520
  "nitro-cloudflare-dev": "^0.2.2",
493
- "@sveltejs/adapter-cloudflare": "^7.2.8",
494
- "@sveltejs/adapter-node": "^5.5.4",
495
- "@cloudflare/workers-types": "^4.20260621.1",
496
- "@astrojs/cloudflare": "^14.0.1",
497
- "@astrojs/node": "^11.0.0",
498
- alchemy: "^0.91.2",
499
- dotenv: "^17.2.2",
500
- tsdown: "^0.21.9",
501
- zod: "^4.1.13",
502
- "@t3-oss/env-core": "^0.13.1",
503
- "@t3-oss/env-nextjs": "^0.13.1",
504
- "@t3-oss/env-nuxt": "^0.13.1",
521
+ "@sveltejs/adapter-cloudflare": "^7.2.9",
522
+ "@sveltejs/adapter-node": "^5.5.7",
523
+ "@sveltejs/adapter-vercel": "^6.3.4",
524
+ "@cloudflare/workers-types": "^4.20260702.1",
525
+ "@astrojs/cloudflare": "^14.1.0",
526
+ "@astrojs/node": "^11.0.1",
527
+ "@astrojs/vercel": "^11.0.1",
528
+ alchemy: "^0.93.12",
529
+ vercel: "^54.18.6",
530
+ dotenv: "^17.4.2",
531
+ tsdown: "^0.22.3",
532
+ zod: "^4.4.3",
533
+ "@t3-oss/env-core": "^0.13.11",
534
+ "@t3-oss/env-nextjs": "^0.13.11",
535
+ "@t3-oss/env-nuxt": "^0.13.11",
505
536
  "@polar-sh/better-auth": "^1.8.4",
506
- "@polar-sh/checkout": "^0.2.1",
537
+ "@polar-sh/checkout": "^0.4.0",
507
538
  "@polar-sh/sdk": "^0.47.1",
508
- "@stripe/react-stripe-js": "^4.0.2",
509
- "@stripe/stripe-js": "^7.9.0",
539
+ "@stripe/react-stripe-js": "^5.6.1",
540
+ "@stripe/stripe-js": "^8.11.0",
510
541
  evlog: "^2.19.2"
511
542
  };
512
543
  /**
@@ -522,8 +553,9 @@ function addPackageDependency(options) {
522
553
  const version = dependencyVersionMap[dep];
523
554
  if (!version) throw new Error(`Missing version for dependency: ${dep}. Add it to dependencyVersionMap in add-deps.ts`);
524
555
  pkgJson.dependencies[dep] = version;
556
+ delete pkgJson.devDependencies[dep];
525
557
  }
526
- for (const dep of devDependencies) if (!pkgJson.devDependencies[dep]) {
558
+ for (const dep of devDependencies) if (!pkgJson.devDependencies[dep] && !pkgJson.dependencies[dep]) {
527
559
  const version = dependencyVersionMap[dep];
528
560
  if (!version) throw new Error(`Missing version for devDependency: ${dep}. Add it to dependencyVersionMap in add-deps.ts`);
529
561
  pkgJson.devDependencies[dep] = version;
@@ -659,10 +691,25 @@ function updateRootPackageJson(vfs, config) {
659
691
  scripts["db:down"] = pmConfig.filter(dbPackageName, "db:down");
660
692
  }
661
693
  const infraPackageName = `@${projectName}/infra`;
662
- if (config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") {
663
- scripts.deploy = pmConfig.filter(infraPackageName, "deploy");
694
+ const hasCloudflareDeploy = config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare";
695
+ const hasVercelDeploy = config.webDeploy === "vercel" || config.serverDeploy === "vercel";
696
+ const isMixedCloud = hasCloudflareDeploy && hasVercelDeploy;
697
+ if (hasCloudflareDeploy) {
698
+ const cfDeployScript = isMixedCloud ? config.webDeploy === "cloudflare" ? "deploy:web" : "deploy:server" : "deploy";
699
+ scripts[cfDeployScript] = pmConfig.filter(infraPackageName, "deploy");
664
700
  scripts.destroy = pmConfig.filter(infraPackageName, "destroy");
665
701
  }
702
+ if (hasVercelDeploy) {
703
+ const vercelTarget = config.webDeploy === "vercel" ? "web" : "server";
704
+ const vercelDeploy = isMixedCloud ? `deploy:${vercelTarget}` : "deploy";
705
+ scripts["deploy:setup"] = "vercel link";
706
+ scripts["dev:vercel"] = "vercel dev -L";
707
+ scripts["env:preview"] = "tsx scripts/sync-vercel-env.ts preview";
708
+ scripts["env:production"] = "tsx scripts/sync-vercel-env.ts production";
709
+ scripts[vercelDeploy] = "vercel deploy";
710
+ scripts[`${vercelDeploy}:prod`] = "vercel deploy --prod";
711
+ scripts["deploy:check"] = "vercel deploy --dry";
712
+ }
666
713
  if (config.webDeploy === "docker" || config.serverDeploy === "docker") {
667
714
  scripts["docker:build"] = "docker compose build";
668
715
  scripts["docker:up"] = "docker compose up -d --build";
@@ -676,8 +723,7 @@ function updateRootPackageJson(vfs, config) {
676
723
  };
677
724
  if (hasVitePlus) pkgJson.overrides = {
678
725
  ...pkgJson.overrides,
679
- vite: `npm:@voidzero-dev/vite-plus-core@${VITE_PLUS_VERSION}`,
680
- vitest: `npm:@voidzero-dev/vite-plus-test@${VITE_PLUS_VERSION}`
726
+ vite: `npm:@voidzero-dev/vite-plus-core@${VITE_PLUS_VERSION}`
681
727
  };
682
728
  if (backend === "convex") {
683
729
  if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
@@ -942,6 +988,86 @@ function updateVitePlusPackageScripts(vfs, config) {
942
988
  vfs.writeJson(webPkgPath, webPkg);
943
989
  }
944
990
  //#endregion
991
+ //#region src/post-process/vercel-config.ts
992
+ function getWebFramework(frontend, isDesktop) {
993
+ if (frontend.includes("next")) return "nextjs";
994
+ if (frontend.includes("nuxt")) return "nuxtjs";
995
+ if (frontend.includes("svelte")) return "sveltekit";
996
+ if (frontend.includes("astro")) return "astro";
997
+ if (frontend.includes("tanstack-start")) return "tanstack-start";
998
+ if (frontend.includes("react-router") && !isDesktop) return "react-router";
999
+ return "vite";
1000
+ }
1001
+ function getPublicServerUrlVar(frontend) {
1002
+ if (frontend.includes("next")) return "NEXT_PUBLIC_SERVER_URL";
1003
+ if (frontend.includes("nuxt")) return "NUXT_PUBLIC_SERVER_URL";
1004
+ if (frontend.includes("svelte") || frontend.includes("astro")) return "PUBLIC_SERVER_URL";
1005
+ return "VITE_SERVER_URL";
1006
+ }
1007
+ function processVercelConfig(vfs, config) {
1008
+ const { webDeploy, serverDeploy, backend, runtime, frontend, addons, packageManager } = config;
1009
+ if (webDeploy !== "vercel" && serverDeploy !== "vercel") return;
1010
+ const hasWeb = webDeploy === "vercel";
1011
+ const hasServer = serverDeploy === "vercel" && backend !== "self";
1012
+ const isDesktop = addons.includes("tauri") || addons.includes("electrobun");
1013
+ const isStaticSpa = frontend.includes("tanstack-router") || frontend.includes("solid") || frontend.includes("react-router") && isDesktop;
1014
+ const installCommand = `cd ../.. && ${packageManager} install`;
1015
+ const services = {};
1016
+ if (hasWeb) {
1017
+ const web = {
1018
+ root: "apps/web",
1019
+ framework: getWebFramework(frontend, isDesktop),
1020
+ installCommand
1021
+ };
1022
+ if (hasServer) web.buildCommand = `${getPublicServerUrlVar(frontend)}=/api ${packageManager} run build`;
1023
+ if (frontend.includes("react-router") && isDesktop) web.outputDirectory = "build/client";
1024
+ if (isStaticSpa) web.rewrites = [{
1025
+ source: "/(.*)",
1026
+ destination: "/index.html"
1027
+ }];
1028
+ services.web = web;
1029
+ }
1030
+ if (hasServer) {
1031
+ const server = {
1032
+ root: "apps/server",
1033
+ framework: backend,
1034
+ entrypoint: "src/index.ts",
1035
+ installCommand
1036
+ };
1037
+ if (hasWeb) server.routes = [{
1038
+ src: "/api/((?!auth(?:/|$)).*)",
1039
+ transforms: [{
1040
+ type: "request.path",
1041
+ op: "set",
1042
+ args: "/$1"
1043
+ }]
1044
+ }];
1045
+ services.server = server;
1046
+ }
1047
+ const rewrites = [];
1048
+ if (hasWeb && hasServer) rewrites.push({
1049
+ source: "/api/(.*)",
1050
+ destination: { service: "server" }
1051
+ }, {
1052
+ source: "/(.*)",
1053
+ destination: { service: "web" }
1054
+ });
1055
+ else if (hasWeb) rewrites.push({
1056
+ source: "/(.*)",
1057
+ destination: { service: "web" }
1058
+ });
1059
+ else if (hasServer) rewrites.push({
1060
+ source: "/(.*)",
1061
+ destination: { service: "server" }
1062
+ });
1063
+ vfs.writeJson("vercel.json", {
1064
+ $schema: "https://openapi.vercel.sh/vercel.json",
1065
+ ...hasServer && runtime === "bun" ? { bunVersion: "1.x" } : {},
1066
+ services,
1067
+ rewrites
1068
+ });
1069
+ }
1070
+ //#endregion
945
1071
  //#region src/processors/addons-deps.ts
946
1072
  function processAddonsDeps(vfs, config) {
947
1073
  if (!config.addons || config.addons.length === 0) return;
@@ -2063,12 +2189,47 @@ function processDrizzleDeps(vfs, config, dbPkgPath, webPkgPath, webExists) {
2063
2189
  //#endregion
2064
2190
  //#region src/processors/deploy-deps.ts
2065
2191
  function processDeployDeps(vfs, config) {
2066
- const { webDeploy, serverDeploy, frontend, backend } = config;
2192
+ const { webDeploy, serverDeploy, frontend, backend, addons } = config;
2067
2193
  const isCloudflareWeb = webDeploy === "cloudflare";
2068
2194
  const isCloudflareServer = serverDeploy === "cloudflare";
2069
2195
  const isDockerWeb = webDeploy === "docker";
2196
+ const isVercelWeb = webDeploy === "vercel";
2197
+ const isVercelServer = serverDeploy === "vercel";
2070
2198
  const isBackendSelf = backend === "self";
2071
- if (!isCloudflareWeb && !isCloudflareServer && !isDockerWeb) return;
2199
+ if (!isCloudflareWeb && !isCloudflareServer && !isDockerWeb && !isVercelWeb && !isVercelServer) return;
2200
+ if (isVercelWeb || isVercelServer) addPackageDependency({
2201
+ vfs,
2202
+ packagePath: "package.json",
2203
+ devDependencies: [
2204
+ "@types/node",
2205
+ "tsx",
2206
+ "vercel"
2207
+ ]
2208
+ });
2209
+ if (isVercelWeb && frontend.includes("tanstack-start")) {
2210
+ const webPkgPath = "apps/web/package.json";
2211
+ if (vfs.exists(webPkgPath)) addPackageDependency({
2212
+ vfs,
2213
+ packagePath: webPkgPath,
2214
+ dependencies: ["nitro"]
2215
+ });
2216
+ }
2217
+ if (isVercelWeb && frontend.includes("astro") && !addons.includes("electrobun") && !addons.includes("tauri")) {
2218
+ const webPkgPath = "apps/web/package.json";
2219
+ if (vfs.exists(webPkgPath)) addPackageDependency({
2220
+ vfs,
2221
+ packagePath: webPkgPath,
2222
+ dependencies: ["@astrojs/vercel"]
2223
+ });
2224
+ }
2225
+ if (isVercelWeb && frontend.includes("svelte") && !addons.includes("electrobun") && !addons.includes("tauri")) {
2226
+ const webPkgPath = "apps/web/package.json";
2227
+ if (vfs.exists(webPkgPath)) addPackageDependency({
2228
+ vfs,
2229
+ packagePath: webPkgPath,
2230
+ devDependencies: ["@sveltejs/adapter-vercel"]
2231
+ });
2232
+ }
2072
2233
  if (isDockerWeb) {
2073
2234
  const webPkgPath = "apps/web/package.json";
2074
2235
  if (vfs.exists(webPkgPath)) {
@@ -2519,10 +2680,25 @@ function processEnvVariables(vfs, config) {
2519
2680
  const hasSvelte = frontend.includes("svelte");
2520
2681
  const hasSolid = frontend.includes("solid");
2521
2682
  const hasAstro = frontend.includes("astro");
2522
- if (hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte || hasAstro) {
2683
+ const hasWebFrontend = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte || hasAstro;
2684
+ if (hasWebFrontend) {
2523
2685
  const clientDir = "apps/web";
2524
2686
  if (vfs.directoryExists(clientDir)) writeEnvFile(vfs, `${clientDir}/.env`, buildClientVars(frontend, backend, auth));
2525
2687
  }
2688
+ if (webDeploy === "docker" && hasWebFrontend) {
2689
+ const rootComposeVars = [{
2690
+ key: hasNextJs ? "NEXT_PUBLIC_CONVEX_URL" : hasSvelte || hasAstro ? "PUBLIC_CONVEX_URL" : "VITE_CONVEX_URL",
2691
+ value: CONVEX_URL_PLACEHOLDER,
2692
+ condition: backend === "convex" && !hasNuxt,
2693
+ comment: "Baked into the web image at docker compose build time"
2694
+ }, {
2695
+ key: hasNextJs ? "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY" : "VITE_CLERK_PUBLISHABLE_KEY",
2696
+ value: "",
2697
+ condition: auth === "clerk" && (hasNextJs || hasReactRouter || hasTanStackRouter || hasTanStackStart),
2698
+ comment: "Baked into the web image at docker compose build time"
2699
+ }];
2700
+ if (rootComposeVars.some((v) => v.condition)) writeEnvFile(vfs, ".env", rootComposeVars);
2701
+ }
2526
2702
  if (frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles")) {
2527
2703
  const nativeDir = "apps/native";
2528
2704
  if (vfs.directoryExists(nativeDir)) writeEnvFile(vfs, `${nativeDir}/.env`, buildNativeVars(frontend, backend, auth));
@@ -2672,7 +2848,7 @@ function setupAIDependencies(vfs, config) {
2672
2848
  //#region src/processors/frontend-deps.ts
2673
2849
  function processFrontendDeps(vfs, config) {
2674
2850
  const { addons, frontend, webDeploy } = config;
2675
- if (!frontend.includes("astro") || addons.includes("electrobun") || addons.includes("tauri") || webDeploy === "cloudflare") return;
2851
+ if (!frontend.includes("astro") || addons.includes("electrobun") || addons.includes("tauri") || webDeploy === "cloudflare" || webDeploy === "vercel") return;
2676
2852
  const webPackagePath = "apps/web/package.json";
2677
2853
  if (!vfs.exists(webPackagePath)) return;
2678
2854
  addPackageDependency({
@@ -2772,6 +2948,7 @@ function getStackGeneratedIgnorePatterns(config) {
2772
2948
  patterns.add("**/.wrangler/**");
2773
2949
  if (config.frontend.includes("next")) patterns.add("apps/web/.open-next/**");
2774
2950
  }
2951
+ if (config.webDeploy === "vercel" || config.serverDeploy === "vercel") patterns.add(".vercel/**");
2775
2952
  return [...patterns];
2776
2953
  }
2777
2954
  //#endregion
@@ -3442,20 +3619,39 @@ function generateScriptsList(packageManagerRunCmd, config, hasNative) {
3442
3619
  - \`${packageManagerRunCmd} docker:up\`: Build and start the Docker Compose stack
3443
3620
  - \`${packageManagerRunCmd} docker:logs\`: Tail logs from the Docker Compose stack
3444
3621
  - \`${packageManagerRunCmd} docker:down\`: Stop the Docker Compose stack`;
3622
+ if (webDeploy === "vercel" || serverDeploy === "vercel") {
3623
+ const v = getVercelScriptNames(webDeploy, serverDeploy);
3624
+ scripts += `\n- \`${packageManagerRunCmd} ${v.setup}\`: Link this repo to a Vercel project (first-time setup)
3625
+ - \`${packageManagerRunCmd} dev:vercel\`: Run the Vercel Services dev environment locally
3626
+ - \`${packageManagerRunCmd} ${v.envPreview}\`: Sync local env files to the Vercel preview environment
3627
+ - \`${packageManagerRunCmd} ${v.envProduction}\`: Sync local env files to the Vercel production environment
3628
+ - \`${packageManagerRunCmd} ${v.deploy}\`: Create a Vercel preview deployment
3629
+ - \`${packageManagerRunCmd} ${v.deployProd}\`: Deploy to Vercel production
3630
+ - \`${packageManagerRunCmd} ${v.deployCheck}\`: Dry-run a deploy to preview framework detection and included files without uploading`;
3631
+ }
3445
3632
  return scripts;
3446
3633
  }
3447
3634
  function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy, backend) {
3448
3635
  const hasCloudflare = webDeploy === "cloudflare" || serverDeploy === "cloudflare";
3449
3636
  const hasDocker = webDeploy === "docker" || serverDeploy === "docker";
3450
- if (!hasCloudflare && !hasDocker) return "";
3637
+ const hasVercel = webDeploy === "vercel" || serverDeploy === "vercel";
3638
+ if (!hasCloudflare && !hasDocker && !hasVercel) return "";
3451
3639
  const lines = ["## Deployment"];
3452
3640
  if (hasCloudflare) {
3453
3641
  const targetLabel = webDeploy === "cloudflare" && (serverDeploy === "cloudflare" || backend === "self") ? "web + server" : webDeploy === "cloudflare" ? "web" : "server";
3454
- lines.push("", "### Cloudflare via Alchemy", "", `- Target: ${targetLabel}`, `- Dev: ${packageManagerRunCmd} dev`, `- Deploy: ${packageManagerRunCmd} deploy`, `- Destroy: ${packageManagerRunCmd} destroy`, "", "For more details, see the guide on [Deploying to Cloudflare with Alchemy](https://www.better-t-stack.dev/docs/guides/cloudflare-alchemy).");
3642
+ const cfDeployScript = hasVercel ? webDeploy === "cloudflare" ? "deploy:web" : "deploy:server" : "deploy";
3643
+ lines.push("", "### Cloudflare via Alchemy", "", `- Target: ${targetLabel}`, `- Dev: ${packageManagerRunCmd} dev`, `- Deploy: ${packageManagerRunCmd} ${cfDeployScript}`, `- Destroy: ${packageManagerRunCmd} destroy`, "", "For more details, see the guide on [Deploying to Cloudflare with Alchemy](https://www.better-t-stack.dev/docs/guides/cloudflare-alchemy).");
3455
3644
  }
3456
3645
  if (hasDocker) {
3457
3646
  const targetLabel = webDeploy === "docker" && (serverDeploy === "docker" || backend === "self") ? "web + server" : webDeploy === "docker" ? "web" : "server";
3458
- lines.push("", "### Docker Compose", "", `- Target: ${targetLabel}`, "- Config: `docker-compose.yml` (app Dockerfiles live in `apps/*/Dockerfile`)", `- Build images: ${packageManagerRunCmd} docker:build`, `- Start: ${packageManagerRunCmd} docker:up`, `- Logs: ${packageManagerRunCmd} docker:logs`, `- Stop: ${packageManagerRunCmd} docker:down`, "", "Environment variables are read from each app's `.env` file (baked into web builds for public variables) and overridden in `docker-compose.yml` for container networking.");
3647
+ lines.push("", "### Docker Compose", "", `- Target: ${targetLabel}`, "- Config: `docker-compose.yml` (app Dockerfiles live in `apps/*/Dockerfile`)", `- Build images: ${packageManagerRunCmd} docker:build`, `- Start: ${packageManagerRunCmd} docker:up`, `- Logs: ${packageManagerRunCmd} docker:logs`, `- Stop: ${packageManagerRunCmd} docker:down`, "", "Environment variables are read from each app's `.env` file (baked into web builds for public variables) and overridden in `docker-compose.yml` for container networking.", "", "For more details, see the guide on [Deploying with Docker Compose](https://www.better-t-stack.dev/docs/guides/docker).");
3648
+ }
3649
+ if (hasVercel) {
3650
+ const vercelNames = getVercelScriptNames(webDeploy, serverDeploy);
3651
+ const targetLabel = webDeploy === "vercel" && (serverDeploy === "vercel" || backend === "self") ? "web + server" : webDeploy === "vercel" ? "web" : "server";
3652
+ lines.push("", "### Vercel Services", "", `- Target: ${targetLabel}`, "- Config: `vercel.json`", `- Link the project first: ${packageManagerRunCmd} ${vercelNames.setup}`, `- Local Vercel dev: ${packageManagerRunCmd} dev:vercel`, `- Sync preview env: ${packageManagerRunCmd} ${vercelNames.envPreview}`, `- Sync production env: ${packageManagerRunCmd} ${vercelNames.envProduction}`, `- Dry-run check (no upload): ${packageManagerRunCmd} ${vercelNames.deployCheck}`, `- Preview deploy: ${packageManagerRunCmd} ${vercelNames.deploy}`, `- Production deploy: ${packageManagerRunCmd} ${vercelNames.deployProd}`);
3653
+ if (webDeploy === "vercel" && serverDeploy === "vercel" && backend !== "self") lines.push("- Web requests under `/api/*` route to the server service and are rewritten before reaching the backend.");
3654
+ lines.push("Vercel Services share project environment variables, but deploys do not upload local `.env` files automatically. Link the project with `vercel link`, then run the env sync command before your first deploy (otherwise the deployment starts with no env vars), or pass one-off envs with `vercel deploy -e KEY=value`.", `Pass Vercel CLI flags to the env sync command directly, for example: \`${packageManagerRunCmd} ${vercelNames.envProduction} --scope your-team\`.`, "", "For more details, see the guide on [Deploying to Vercel](https://www.better-t-stack.dev/docs/guides/vercel).");
3459
3655
  }
3460
3656
  return `${lines.join("\n")}\n`;
3461
3657
  }
@@ -3472,6 +3668,17 @@ function generateGitHooksSection(packageManagerRunCmd, addons) {
3472
3668
  if (hasLinting) lines.push(`- Run checks: \`${packageManagerRunCmd} check\``);
3473
3669
  return `${lines.join("\n")}\n\n`;
3474
3670
  }
3671
+ function getVercelScriptNames(webDeploy, serverDeploy) {
3672
+ const deploy = webDeploy === "cloudflare" || serverDeploy === "cloudflare" ? `deploy:${webDeploy === "vercel" ? "web" : "server"}` : "deploy";
3673
+ return {
3674
+ setup: "deploy:setup",
3675
+ envPreview: "env:preview",
3676
+ envProduction: "env:production",
3677
+ deploy,
3678
+ deployProd: `${deploy}:prod`,
3679
+ deployCheck: "deploy:check"
3680
+ };
3681
+ }
3475
3682
  //#endregion
3476
3683
  //#region src/processors/runtime-deps.ts
3477
3684
  function processRuntimeDeps(vfs, config) {
@@ -4219,7 +4426,7 @@ async function processExtrasTemplates(vfs, templates, config) {
4219
4426
  ].includes(f));
4220
4427
  const hasNuxt = config.frontend.includes("nuxt");
4221
4428
  if (config.packageManager === "pnpm") processSingleTemplate(vfs, templates, "extras/pnpm-workspace.yaml", "pnpm-workspace.yaml", config);
4222
- if (config.packageManager === "bun") processSingleTemplate(vfs, templates, "extras/bunfig.toml", "bunfig.toml", config);
4429
+ if (config.packageManager === "bun" && hasNative) processSingleTemplate(vfs, templates, "extras/bunfig.toml", "bunfig.toml", config);
4223
4430
  if (config.packageManager === "pnpm" && (hasNative || hasNuxt)) processSingleTemplate(vfs, templates, "extras/_npmrc", ".npmrc", config);
4224
4431
  if (config.serverDeploy === "cloudflare" || config.backend === "self" && config.webDeploy === "cloudflare") processSingleTemplate(vfs, templates, "extras/env.d.ts", "packages/env/env.d.ts", config);
4225
4432
  }
@@ -4229,7 +4436,8 @@ async function processDeployTemplates(vfs, templates, config) {
4229
4436
  const isBackendSelf = config.backend === "self";
4230
4437
  if (config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") processTemplatesFromPrefix(vfs, templates, "packages/infra", "packages/infra", config);
4231
4438
  if (config.webDeploy === "docker" || config.serverDeploy === "docker") processTemplatesFromPrefix(vfs, templates, "deploy/docker/compose", "", config);
4232
- if (config.webDeploy !== "none" && config.webDeploy !== "cloudflare") {
4439
+ if (config.webDeploy === "vercel" || config.serverDeploy === "vercel") processTemplatesFromPrefix(vfs, templates, "deploy/vercel", "", config);
4440
+ if (config.webDeploy !== "none" && config.webDeploy !== "cloudflare" && config.webDeploy !== "vercel") {
4233
4441
  const templateMap = {
4234
4442
  "tanstack-router": "react/tanstack-router",
4235
4443
  "tanstack-start": "react/tanstack-start",
@@ -4242,7 +4450,7 @@ async function processDeployTemplates(vfs, templates, config) {
4242
4450
  };
4243
4451
  for (const f of config.frontend) if (templateMap[f]) processTemplatesFromPrefix(vfs, templates, `deploy/${config.webDeploy}/web/${templateMap[f]}`, "apps/web", config);
4244
4452
  }
4245
- if (config.serverDeploy !== "none" && config.serverDeploy !== "cloudflare" && !isBackendSelf) processTemplatesFromPrefix(vfs, templates, `deploy/${config.serverDeploy}/server`, "apps/server", config);
4453
+ if (config.serverDeploy !== "none" && config.serverDeploy !== "cloudflare" && config.serverDeploy !== "vercel" && !isBackendSelf) processTemplatesFromPrefix(vfs, templates, `deploy/${config.serverDeploy}/server`, "apps/server", config);
4246
4454
  }
4247
4455
  //#endregion
4248
4456
  //#region src/utils/reproducible-command.ts
@@ -4330,6 +4538,7 @@ async function generate(options) {
4330
4538
  processAlchemyPlugins(vfs, config);
4331
4539
  processPwaPlugins(vfs, config);
4332
4540
  processCatalogs(vfs, config);
4541
+ processVercelConfig(vfs, config);
4333
4542
  processReadme(vfs, config);
4334
4543
  if (options.version) {
4335
4544
  const reproducibleCommand = generateReproducibleCommand(config);
@@ -4502,7 +4711,6 @@ export default {
4502
4711
  },
4503
4712
  "devDependencies": {
4504
4713
  "@types/bun": "^1.3.14",
4505
- "@types/three": "^0.165.0",
4506
4714
  "concurrently": "^10.0.3",
4507
4715
  "typescript": "^6"
4508
4716
  }
@@ -5612,13 +5820,16 @@ import { RPCLink } from "@orpc/client/fetch";
5612
5820
 
5613
5821
  {{#if (eq backend "self")}}
5614
5822
  export const link = new RPCLink({
5615
- url: \`\${window.location.origin}/rpc\`,
5823
+ // Lazy: this module is imported during SSR where window does not exist
5824
+ url: () => \`\${window.location.origin}/rpc\`,
5616
5825
  });
5617
5826
  {{else}}
5618
5827
  import { PUBLIC_SERVER_URL } from "astro:env/client";
5619
5828
 
5829
+ {{> getServerUrlSpaces}}
5830
+
5620
5831
  export const link = new RPCLink({
5621
- url: \`\${PUBLIC_SERVER_URL}/rpc\`,
5832
+ url: \`\${getServerUrl(PUBLIC_SERVER_URL)}/rpc\`,
5622
5833
  {{#if (eq auth "better-auth")}}
5623
5834
  fetch(url, options) {
5624
5835
  return fetch(url, {
@@ -5638,11 +5849,25 @@ import { createORPCClient } from '@orpc/client'
5638
5849
  import { RPCLink } from '@orpc/client/fetch'
5639
5850
  import { createTanstackQueryUtils } from "@orpc/tanstack-query";
5640
5851
 
5852
+ function getServerUrl(url: string) {
5853
+ const normalized = url.endsWith("/") ? url.slice(0, -1) : url;
5854
+
5855
+ if (!normalized.startsWith("/")) {
5856
+ return normalized;
5857
+ }
5858
+
5859
+ if (import.meta.server) {
5860
+ return \`\${useRequestURL().origin}\${normalized}\`;
5861
+ }
5862
+
5863
+ return \`\${window.location.origin}\${normalized}\`;
5864
+ }
5865
+
5641
5866
  export default defineNuxtPlugin(() => {
5642
5867
  const config = useRuntimeConfig();
5643
5868
  const serverUrl =
5644
5869
  (import.meta.server && config.serverUrl) || config.public.serverUrl;
5645
- const rpcUrl = \`\${serverUrl}/rpc\`;
5870
+ const rpcUrl = \`\${getServerUrl(serverUrl)}/rpc\`;
5646
5871
 
5647
5872
  const rpcLink = new RPCLink({
5648
5873
  url: rpcUrl,
@@ -5770,6 +5995,10 @@ export function createQueryClient() {
5770
5995
  export const queryClient = createQueryClient();
5771
5996
  {{/unless}}
5772
5997
 
5998
+ {{#unless (eq backend "self")}}
5999
+ {{> getServerUrl}}
6000
+
6001
+ {{/unless}}
5773
6002
  {{#if (and (includes frontend "tanstack-start") (eq backend "self"))}}
5774
6003
  const getORPCClient = createIsomorphicFn()
5775
6004
  .server(() =>
@@ -5798,7 +6027,7 @@ const getORPCClient = createIsomorphicFn()
5798
6027
  export const client: RouterClient<typeof appRouter> = getORPCClient();
5799
6028
  {{else if (includes frontend "tanstack-start")}}
5800
6029
  const link = new RPCLink({
5801
- url: \`\${env.VITE_SERVER_URL}/rpc\`,
6030
+ url: \`\${getServerUrl(env.VITE_SERVER_URL)}/rpc\`,
5802
6031
  {{#if (eq auth "clerk")}}
5803
6032
  headers: async () => {
5804
6033
  const token = await getClerkAuthToken();
@@ -5825,9 +6054,9 @@ export const link = new RPCLink({
5825
6054
  {{#if (and (eq backend "self") (includes frontend "next"))}}
5826
6055
  url: \`\${typeof window !== "undefined" ? window.location.origin : "http://localhost:3001"}/api/rpc\`,
5827
6056
  {{else if (includes frontend "next")}}
5828
- url: \`\${env.NEXT_PUBLIC_SERVER_URL}/rpc\`,
6057
+ url: \`\${getServerUrl(env.NEXT_PUBLIC_SERVER_URL)}/rpc\`,
5829
6058
  {{else}}
5830
- url: \`\${env.VITE_SERVER_URL}/rpc\`,
6059
+ url: \`\${getServerUrl(env.VITE_SERVER_URL)}/rpc\`,
5831
6060
  {{/if}}
5832
6061
  {{#if (eq auth "clerk")}}
5833
6062
  headers: async () => {
@@ -5888,8 +6117,10 @@ export const queryClient = new QueryClient({
5888
6117
  }),
5889
6118
  });
5890
6119
 
6120
+ {{> getServerUrl}}
6121
+
5891
6122
  export const link = new RPCLink({
5892
- url: \`\${env.VITE_SERVER_URL}/rpc\`,
6123
+ url: \`\${getServerUrl(env.VITE_SERVER_URL)}/rpc\`,
5893
6124
  {{#if (eq auth "better-auth")}}
5894
6125
  fetch(url, options) {
5895
6126
  return fetch(url, {
@@ -5921,6 +6152,10 @@ export const queryClient = new QueryClient({
5921
6152
  }),
5922
6153
  });
5923
6154
 
6155
+ {{#unless (eq backend "self")}}
6156
+ {{> getServerUrl}}
6157
+
6158
+ {{/unless}}
5924
6159
  export const link = new RPCLink({
5925
6160
  {{#if (eq backend "self")}}
5926
6161
  url: () => {
@@ -5931,7 +6166,7 @@ export const link = new RPCLink({
5931
6166
  return \`\${window.location.origin}/rpc\`;
5932
6167
  },
5933
6168
  {{else}}
5934
- url: \`\${PUBLIC_SERVER_URL}/rpc\`,
6169
+ url: \`\${getServerUrl(PUBLIC_SERVER_URL)}/rpc\`,
5935
6170
  {{/if}}
5936
6171
  {{#if (eq auth "better-auth")}}
5937
6172
  fetch(url, options) {
@@ -6457,6 +6692,8 @@ import type { AppRouter } from "@{{projectName}}/api/routers/index";
6457
6692
  import { toast } from 'sonner';
6458
6693
  {{#unless (eq backend "self")}}
6459
6694
  import { env } from "@{{projectName}}/env/web";
6695
+
6696
+ {{> getServerUrl}}
6460
6697
  {{/unless}}
6461
6698
  {{#if (eq auth "clerk")}}
6462
6699
  import { getClerkAuthToken } from "@/utils/clerk-auth";
@@ -6483,7 +6720,7 @@ const trpcClient = createTRPCClient<AppRouter>({
6483
6720
  {{#if (eq backend "self")}}
6484
6721
  url: "/api/trpc",
6485
6722
  {{else}}
6486
- url: \`\${env.NEXT_PUBLIC_SERVER_URL}/trpc\`,
6723
+ url: \`\${getServerUrl(env.NEXT_PUBLIC_SERVER_URL)}/trpc\`,
6487
6724
  {{/if}}
6488
6725
  {{#if (eq auth "clerk")}}
6489
6726
  headers: async () => {
@@ -6534,6 +6771,8 @@ import { env } from "@{{projectName}}/env/web";
6534
6771
  import { getClerkAuthToken } from "@/utils/clerk-auth";
6535
6772
  {{/if}}
6536
6773
 
6774
+ {{> getServerUrl}}
6775
+
6537
6776
  export const queryClient = new QueryClient({
6538
6777
  queryCache: new QueryCache({
6539
6778
  onError: (error, query) => {
@@ -6552,7 +6791,7 @@ export const queryClient = new QueryClient({
6552
6791
  export const trpcClient = createTRPCClient<AppRouter>({
6553
6792
  links: [
6554
6793
  httpBatchLink({
6555
- url: \`\${env.VITE_SERVER_URL}/trpc\`,
6794
+ url: \`\${getServerUrl(env.VITE_SERVER_URL)}/trpc\`,
6556
6795
  {{#if (eq auth "clerk")}}
6557
6796
  headers: async () => {
6558
6797
  const token = await getClerkAuthToken();
@@ -13348,9 +13587,15 @@ import { polarClient } from "@polar-sh/better-auth/client";
13348
13587
  import { PUBLIC_SERVER_URL } from "astro:env/client";
13349
13588
  {{/if}}
13350
13589
 
13590
+ {{#if (ne backend "self")}}
13591
+ {{> getServerUrlSpaces}}
13592
+
13593
+ {{/if}}
13351
13594
  export const authClient = createAuthClient({
13352
13595
  {{#if (ne backend "self")}}
13353
- baseURL: PUBLIC_SERVER_URL,
13596
+ // better-auth derives its route-matching base from this URL's path, so the
13597
+ // public auth path must equal the server-side mount (/api/auth everywhere)
13598
+ baseURL: new URL("/api/auth", getServerUrl(PUBLIC_SERVER_URL)).toString(),
13354
13599
  {{/if}}
13355
13600
  {{#if (eq payments "polar")}}
13356
13601
  plugins: [polarClient()],
@@ -13884,11 +14129,17 @@ import { polarClient } from "@polar-sh/better-auth/client";
13884
14129
  export default defineNuxtPlugin(() => {
13885
14130
  {{#if (ne backend "self")}}
13886
14131
  const config = useRuntimeConfig();
14132
+ const rawServerUrl = (import.meta.server && config.serverUrl) || config.public.serverUrl;
14133
+ // Same-origin paths like /api need an absolute base, and better-auth derives
14134
+ // its route matching from this URL's path, so it must be exactly /api/auth
14135
+ const serverOrigin = rawServerUrl.startsWith("/")
14136
+ ? (import.meta.server ? useRequestURL() : window.location).origin + rawServerUrl
14137
+ : rawServerUrl;
13887
14138
  {{/if}}
13888
14139
 
13889
14140
  const authClient = createAuthClient({
13890
14141
  {{#if (ne backend "self")}}
13891
- baseURL: (import.meta.server && config.serverUrl) || config.public.serverUrl,
14142
+ baseURL: new URL("/api/auth", serverOrigin).toString(),
13892
14143
  {{/if}}
13893
14144
  {{#if (eq payments "polar")}}
13894
14145
  plugins: [polarClient()],
@@ -13908,11 +14159,15 @@ import { polarClient } from "@polar-sh/better-auth/client";
13908
14159
  {{/if}}
13909
14160
  {{#unless (eq backend "self")}}
13910
14161
  import { env } from "@{{projectName}}/env/web";
14162
+
14163
+ {{> getServerUrl}}
13911
14164
  {{/unless}}
13912
14165
 
13913
14166
  export const authClient = createAuthClient({
13914
14167
  {{#unless (eq backend "self")}}
13915
- baseURL: env.{{#if (includes frontend "next")}}NEXT_PUBLIC_SERVER_URL{{else}}VITE_SERVER_URL{{/if}},
14168
+ // better-auth derives its route-matching base from this URL's path, so the
14169
+ // public auth path must equal the server-side mount (/api/auth everywhere)
14170
+ baseURL: new URL("/api/auth", getServerUrl(env.{{#if (includes frontend "next")}}NEXT_PUBLIC_SERVER_URL{{else}}VITE_SERVER_URL{{/if}})).toString(),
13916
14171
  {{/unless}}
13917
14172
  {{#if (eq payments "polar")}}
13918
14173
  plugins: [polarClient()]
@@ -16206,8 +16461,12 @@ import { polarClient } from "@polar-sh/better-auth/client";
16206
16461
  {{/if}}
16207
16462
  import { env } from "@{{projectName}}/env/web";
16208
16463
 
16464
+ {{> getServerUrl}}
16465
+
16209
16466
  export const authClient = createAuthClient({
16210
- baseURL: env.VITE_SERVER_URL,
16467
+ // better-auth derives its route-matching base from this URL's path, so the
16468
+ // public auth path must equal the server-side mount (/api/auth everywhere)
16469
+ baseURL: new URL("/api/auth", getServerUrl(env.VITE_SERVER_URL)).toString(),
16211
16470
  {{#if (eq payments "polar")}}
16212
16471
  plugins: [polarClient()]
16213
16472
  {{/if}}
@@ -16623,9 +16882,15 @@ import { createAuthClient } from "better-auth/svelte";
16623
16882
  import { polarClient } from "@polar-sh/better-auth/client";
16624
16883
  {{/if}}
16625
16884
 
16885
+ {{#unless (eq backend "self")}}
16886
+ {{> getServerUrl}}
16887
+
16888
+ {{/unless}}
16626
16889
  export const authClient = createAuthClient({
16627
16890
  {{#unless (eq backend "self")}}
16628
- baseURL: PUBLIC_SERVER_URL,
16891
+ // better-auth derives its route-matching base from this URL's path, so the
16892
+ // public auth path must equal the server-side mount (/api/auth everywhere)
16893
+ baseURL: new URL("/api/auth", getServerUrl(PUBLIC_SERVER_URL)).toString(),
16629
16894
  {{/unless}}
16630
16895
  {{#if (eq payments "polar")}}
16631
16896
  plugins: [polarClient()]
@@ -16646,7 +16911,7 @@ export const authClient = createAuthClient({
16646
16911
  const sessionQuery = authClient.useSession();
16647
16912
 
16648
16913
  {{#if (eq api "orpc")}}
16649
- const privateDataQuery = createQuery(orpc.privateData.queryOptions());
16914
+ const privateDataQuery = createQuery(() => orpc.privateData.queryOptions());
16650
16915
  {{/if}}
16651
16916
 
16652
16917
  $effect(() => {
@@ -16675,7 +16940,7 @@ export const authClient = createAuthClient({
16675
16940
  <h1>Dashboard</h1>
16676
16941
  <p>Welcome {$sessionQuery.data.user.name}</p>
16677
16942
  {{#if (eq api "orpc")}}
16678
- <p>API: {$privateDataQuery.data?.message}</p>
16943
+ <p>API: {privateDataQuery.data?.message}</p>
16679
16944
  {{/if}}
16680
16945
  {{#if (eq payments "polar")}}
16681
16946
  <p>Plan: {customerState?.activeSubscriptions?.length > 0 ? "Pro" : "Free"}</p>
@@ -18651,11 +18916,7 @@ const apiHandler = new OpenAPIHandler(appRouter, {
18651
18916
  });
18652
18917
  {{/if}}
18653
18918
 
18654
- {{#if (eq runtime "node")}}
18655
- new Elysia({ adapter: node() })
18656
- {{else}}
18657
- new Elysia()
18658
- {{/if}}
18919
+ const app = {{#if (eq runtime "node")}}new Elysia({ adapter: node() }){{else}}new Elysia(){{/if}}
18659
18920
  .use(
18660
18921
  cors({
18661
18922
  origin: env.CORS_ORIGIN,
@@ -18761,9 +19022,22 @@ new Elysia()
18761
19022
  })
18762
19023
  {{/if}}
18763
19024
  .get("/", () => "OK")
19025
+ {{#if (eq serverDeploy "vercel")}};
19026
+
19027
+ export default app;
19028
+
19029
+ // Elysia's default export is not auto-served by Bun or Node, so start a local
19030
+ // server outside Vercel while still exporting the app for Vercel functions.
19031
+ if (!process.env.VERCEL) {
19032
+ app.listen(3000, () => {
19033
+ console.log("Server is running on http://localhost:3000");
19034
+ });
19035
+ }
19036
+ {{else}}
18764
19037
  .listen(3000, () => {
18765
19038
  console.log("Server is running on http://localhost:3000");
18766
19039
  });
19040
+ {{/if}}
18767
19041
  `],
18768
19042
  ["backend/server/express/src/index.ts.hbs", `import { env } from "@{{projectName}}/env/server";
18769
19043
  {{#if (eq api "trpc")}}
@@ -19334,15 +19608,23 @@ app.get("/", (c) => {
19334
19608
  {{#if (eq runtime "node")}}
19335
19609
  import { serve } from "@hono/node-server";
19336
19610
 
19337
- serve(
19338
- {
19339
- fetch: app.fetch,
19340
- port: 3000,
19341
- },
19342
- (info) => {
19343
- console.log(\`Server is running on http://localhost:\${info.port}\`);
19344
- }
19345
- );
19611
+ {{#if (eq serverDeploy "vercel")}}
19612
+ export default app;
19613
+
19614
+ if (!process.env.VERCEL) {
19615
+ {{/if}}
19616
+ serve(
19617
+ {
19618
+ fetch: app.fetch,
19619
+ port: 3000,
19620
+ },
19621
+ (info) => {
19622
+ console.log(\`Server is running on http://localhost:\${info.port}\`);
19623
+ }
19624
+ );
19625
+ {{#if (eq serverDeploy "vercel")}}
19626
+ }
19627
+ {{/if}}
19346
19628
  {{else}}
19347
19629
  {{#if (eq runtime "bun")}}
19348
19630
  export default app;
@@ -19396,6 +19678,7 @@ lerna-debug.log*
19396
19678
 
19397
19679
  # Better-T-Stack
19398
19680
  .alchemy
19681
+ .vercel
19399
19682
 
19400
19683
  # Testing
19401
19684
  coverage
@@ -19426,7 +19709,7 @@ temp
19426
19709
 
19427
19710
  services:
19428
19711
  mongodb:
19429
- image: mongo
19712
+ image: mongo:8
19430
19713
  container_name: {{projectName}}-mongodb
19431
19714
  environment:
19432
19715
  MONGO_INITDB_ROOT_USERNAME: root
@@ -19449,7 +19732,7 @@ volumes:
19449
19732
 
19450
19733
  services:
19451
19734
  mysql:
19452
- image: mysql
19735
+ image: mysql:8.4
19453
19736
  container_name: {{projectName}}-mysql
19454
19737
  environment:
19455
19738
  MYSQL_ROOT_PASSWORD: password
@@ -19473,7 +19756,7 @@ volumes:
19473
19756
 
19474
19757
  services:
19475
19758
  postgres:
19476
- image: postgres
19759
+ image: postgres:18
19477
19760
  container_name: {{projectName}}-postgres
19478
19761
  environment:
19479
19762
  POSTGRES_DB: {{projectName}}
@@ -20257,15 +20540,15 @@ services:
20257
20540
  {{#if (includes frontend "next")}}NEXT_PUBLIC_SERVER_URL{{else if (or (includes frontend "svelte") (includes frontend "astro"))}}PUBLIC_SERVER_URL{{else}}VITE_SERVER_URL{{/if}}: http://localhost:3000
20258
20541
  {{/if}}
20259
20542
  {{#if (eq backend "convex")}}
20260
- {{#if (includes frontend "next")}}NEXT_PUBLIC_CONVEX_URL{{else if (or (includes frontend "svelte") (includes frontend "astro"))}}PUBLIC_CONVEX_URL{{else}}VITE_CONVEX_URL{{/if}}: \${CONVEX_URL:-}
20543
+ {{#if (includes frontend "next")}}NEXT_PUBLIC_CONVEX_URL: \${NEXT_PUBLIC_CONVEX_URL:-}{{else if (or (includes frontend "svelte") (includes frontend "astro"))}}PUBLIC_CONVEX_URL: \${PUBLIC_CONVEX_URL:-}{{else}}VITE_CONVEX_URL: \${VITE_CONVEX_URL:-}{{/if}}
20261
20544
  {{/if}}
20262
20545
  {{#if (and (eq auth "clerk") (or (includes frontend "next") (includes frontend "react-router") (includes frontend "tanstack-router") (includes frontend "tanstack-start")))}}
20263
- {{#if (includes frontend "next")}}NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY{{else}}VITE_CLERK_PUBLISHABLE_KEY{{/if}}: \${CLERK_PUBLISHABLE_KEY:-}
20546
+ {{#if (includes frontend "next")}}NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: \${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:-}{{else}}VITE_CLERK_PUBLISHABLE_KEY: \${VITE_CLERK_PUBLISHABLE_KEY:-}{{/if}}
20264
20547
  {{/if}}
20265
20548
  {{/if}}
20266
20549
  init: true
20267
20550
  ports:
20268
- {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "solid"))}}
20551
+ {{#if (or (includes frontend "tanstack-router") (includes frontend "solid"))}}
20269
20552
  - "3001:80"
20270
20553
  {{else}}
20271
20554
  - "3001:3001"
@@ -20311,6 +20594,22 @@ services:
20311
20594
  condition: service_healthy
20312
20595
  {{/if}}
20313
20596
  {{/if}}
20597
+ healthcheck:
20598
+ {{#if (or (includes frontend "tanstack-router") (includes frontend "solid"))}}
20599
+ test: ["CMD", "wget", "-q", "--spider", "http://localhost:80/"]
20600
+ {{else}}
20601
+ test:
20602
+ [
20603
+ "CMD",
20604
+ "node",
20605
+ "-e",
20606
+ "fetch('http://localhost:3001/').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))",
20607
+ ]
20608
+ {{/if}}
20609
+ interval: 10s
20610
+ timeout: 5s
20611
+ retries: 5
20612
+ start_period: 10s
20314
20613
  restart: unless-stopped
20315
20614
 
20316
20615
  {{/if}}
@@ -20363,7 +20662,7 @@ services:
20363
20662
  {{#if (eq dbSetup "docker")}}
20364
20663
  {{#if (eq database "postgres")}}
20365
20664
  postgres:
20366
- image: postgres
20665
+ image: postgres:18
20367
20666
  container_name: {{projectName}}-postgres
20368
20667
  environment:
20369
20668
  POSTGRES_DB: {{projectName}}
@@ -20382,7 +20681,7 @@ services:
20382
20681
  {{/if}}
20383
20682
  {{#if (eq database "mysql")}}
20384
20683
  mysql:
20385
- image: mysql
20684
+ image: mysql:8.4
20386
20685
  container_name: {{projectName}}-mysql
20387
20686
  environment:
20388
20687
  MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-password}
@@ -20402,7 +20701,7 @@ services:
20402
20701
  {{/if}}
20403
20702
  {{#if (eq database "mongodb")}}
20404
20703
  mongodb:
20405
- image: mongo
20704
+ image: mongo:8
20406
20705
  container_name: {{projectName}}-mongodb
20407
20706
  environment:
20408
20707
  MONGO_INITDB_ROOT_USERNAME: root
@@ -20429,7 +20728,7 @@ volumes:
20429
20728
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20430
20729
  {{/if}}
20431
20730
  {{#if (eq packageManager "pnpm")}}
20432
- RUN npm install -g pnpm
20731
+ RUN npm install -g pnpm@11
20433
20732
  {{/if}}
20434
20733
  WORKDIR /app
20435
20734
  ENV SKIP_ENV_VALIDATION=1
@@ -20468,7 +20767,7 @@ CMD ["node", "dist/index.mjs"]
20468
20767
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20469
20768
  {{/if}}
20470
20769
  {{#if (eq packageManager "pnpm")}}
20471
- RUN npm install -g pnpm
20770
+ RUN npm install -g pnpm@11
20472
20771
  {{/if}}
20473
20772
  WORKDIR /app
20474
20773
  ENV SKIP_ENV_VALIDATION=1
@@ -20520,7 +20819,7 @@ CMD ["node", "dist/server/entry.mjs"]
20520
20819
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20521
20820
  {{/if}}
20522
20821
  {{#if (eq packageManager "pnpm")}}
20523
- RUN npm install -g pnpm
20822
+ RUN npm install -g pnpm@11
20524
20823
  {{/if}}
20525
20824
  WORKDIR /app
20526
20825
  ENV SKIP_ENV_VALIDATION=1
@@ -20562,7 +20861,7 @@ CMD ["node", "server/index.mjs"]
20562
20861
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20563
20862
  {{/if}}
20564
20863
  {{#if (eq packageManager "pnpm")}}
20565
- RUN npm install -g pnpm
20864
+ RUN npm install -g pnpm@11
20566
20865
  {{/if}}
20567
20866
  WORKDIR /app
20568
20867
  ENV SKIP_ENV_VALIDATION=1
@@ -20598,6 +20897,8 @@ ENV NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=\${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
20598
20897
  {{/if}}
20599
20898
  ENV NODE_ENV=production
20600
20899
  RUN cd apps/web && {{packageManager}} run build
20900
+ # standalone output excludes public/; ensure it exists so the runner copy never fails
20901
+ RUN mkdir -p apps/web/public
20601
20902
 
20602
20903
  FROM node:24-slim AS runner
20603
20904
  WORKDIR /app
@@ -20605,6 +20906,7 @@ ENV NODE_ENV=production
20605
20906
 
20606
20907
  COPY --from=builder /app/apps/web/.next/standalone ./
20607
20908
  COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
20909
+ COPY --from=builder /app/apps/web/public ./apps/web/public
20608
20910
 
20609
20911
  ENV HOSTNAME=0.0.0.0
20610
20912
  ENV PORT=3001
@@ -20617,7 +20919,7 @@ CMD ["node", "apps/web/server.js"]
20617
20919
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20618
20920
  {{/if}}
20619
20921
  {{#if (eq packageManager "pnpm")}}
20620
- RUN npm install -g pnpm
20922
+ RUN npm install -g pnpm@11
20621
20923
  {{/if}}
20622
20924
  WORKDIR /app
20623
20925
  ENV SKIP_ENV_VALIDATION=1
@@ -20654,37 +20956,21 @@ ENV VITE_CLERK_PUBLISHABLE_KEY=\${VITE_CLERK_PUBLISHABLE_KEY}
20654
20956
  ENV NODE_ENV=production
20655
20957
  RUN cd apps/web && {{packageManager}} run build
20656
20958
 
20657
- FROM nginx:alpine AS runner
20658
- COPY --from=builder /app/apps/web/build/client /usr/share/nginx/html
20659
- COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
20660
- EXPOSE 80
20661
- CMD ["nginx", "-g", "daemon off;"]
20662
- `],
20663
- ["deploy/docker/web/react/react-router/nginx.conf", `server {
20664
- listen 80;
20665
- server_name _;
20666
- root /usr/share/nginx/html;
20667
- index index.html;
20668
-
20669
- gzip on;
20670
- gzip_types text/plain text/css application/json application/javascript image/svg+xml;
20671
-
20672
- location /assets/ {
20673
- add_header Cache-Control "public, max-age=31536000, immutable";
20674
- }
20675
-
20676
- # SPA fallback
20677
- location / {
20678
- try_files $uri $uri/ /index.html;
20679
- }
20680
- }
20959
+ FROM node:24-slim AS runner
20960
+ WORKDIR /app
20961
+ COPY --from=builder /app .
20962
+ ENV NODE_ENV=production
20963
+ ENV PORT=3001
20964
+ ENV HOST=0.0.0.0
20965
+ EXPOSE 3001
20966
+ CMD ["sh", "-c", "cd apps/web && npm run start"]
20681
20967
  `],
20682
20968
  ["deploy/docker/web/react/tanstack-router/Dockerfile.hbs", `FROM node:24{{#unless (includes addons "vite-plus")}}-slim{{/unless}} AS builder
20683
20969
  {{#if (eq packageManager "bun")}}
20684
20970
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20685
20971
  {{/if}}
20686
20972
  {{#if (eq packageManager "pnpm")}}
20687
- RUN npm install -g pnpm
20973
+ RUN npm install -g pnpm@11
20688
20974
  {{/if}}
20689
20975
  WORKDIR /app
20690
20976
  ENV SKIP_ENV_VALIDATION=1
@@ -20751,7 +21037,7 @@ CMD ["nginx", "-g", "daemon off;"]
20751
21037
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20752
21038
  {{/if}}
20753
21039
  {{#if (eq packageManager "pnpm")}}
20754
- RUN npm install -g pnpm
21040
+ RUN npm install -g pnpm@11
20755
21041
  {{/if}}
20756
21042
  WORKDIR /app
20757
21043
  ENV SKIP_ENV_VALIDATION=1
@@ -20808,7 +21094,7 @@ CMD ["node", ".output/server/index.mjs"]
20808
21094
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20809
21095
  {{/if}}
20810
21096
  {{#if (eq packageManager "pnpm")}}
20811
- RUN npm install -g pnpm
21097
+ RUN npm install -g pnpm@11
20812
21098
  {{/if}}
20813
21099
  WORKDIR /app
20814
21100
  ENV SKIP_ENV_VALIDATION=1
@@ -20871,7 +21157,7 @@ CMD ["nginx", "-g", "daemon off;"]
20871
21157
  COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20872
21158
  {{/if}}
20873
21159
  {{#if (eq packageManager "pnpm")}}
20874
- RUN npm install -g pnpm
21160
+ RUN npm install -g pnpm@11
20875
21161
  {{/if}}
20876
21162
  WORKDIR /app
20877
21163
  ENV SKIP_ENV_VALIDATION=1
@@ -20917,6 +21203,143 @@ EXPOSE 3001
20917
21203
 
20918
21204
  WORKDIR /app/apps/web
20919
21205
  CMD ["node", "build/index.js"]
21206
+ `],
21207
+ ["deploy/vercel/_vercelignore", `# Local env files must never ship in deployments: Vercel project env vars are
21208
+ # the source of truth (bun env:vercel:*), and frameworks like Next.js would
21209
+ # otherwise load these localhost values at runtime.
21210
+ .env
21211
+ .env.*
21212
+ **/.env
21213
+ **/.env.*
21214
+ !**/.env.example
21215
+ local.db
21216
+ local.db-*
21217
+ .alchemy/
21218
+ `],
21219
+ ["deploy/vercel/scripts/sync-vercel-env.ts.hbs", `import { spawnSync } from "node:child_process";
21220
+ import { existsSync, readFileSync } from "node:fs";
21221
+ import dotenv from "dotenv";
21222
+
21223
+ const DEFAULT_ENVIRONMENT = "preview";
21224
+ const VALID_ENVIRONMENTS = new Set(["development", "preview", "production"]);
21225
+ const VERCEL_COMMAND = [{{#if (eq packageManager "npm")}}"npx", "vercel"{{else if (eq packageManager "pnpm")}}"pnpm", "exec", "vercel"{{else}}"bunx", "vercel"{{/if}}] as const;
21226
+ const DEFAULT_FILES = [
21227
+ {{#if (eq webDeploy "vercel")}}
21228
+ "apps/web/.env",
21229
+ {{/if}}
21230
+ {{#if (and (eq serverDeploy "vercel") (ne backend "self") (ne backend "none") (ne backend "convex"))}}
21231
+ "apps/server/.env",
21232
+ {{/if}}
21233
+ ];
21234
+ const SKIP_KEYS = new Set([
21235
+ {{#if (or (and (eq webDeploy "vercel") (eq serverDeploy "vercel") (ne backend "self") (ne backend "none") (ne backend "convex")) (and (eq webDeploy "vercel") (eq backend "self")))}}
21236
+ "BETTER_AUTH_URL",
21237
+ "CORS_ORIGIN",
21238
+ "NODE_ENV",
21239
+ {{/if}}
21240
+ ]);
21241
+ const OVERRIDE_KEYS = new Map([
21242
+ {{#if (and (eq webDeploy "vercel") (eq serverDeploy "vercel") (ne backend "self") (ne backend "none") (ne backend "convex"))}}
21243
+ ["NEXT_PUBLIC_SERVER_URL", "/api"],
21244
+ ["NUXT_PUBLIC_SERVER_URL", "/api"],
21245
+ ["PUBLIC_SERVER_URL", "/api"],
21246
+ ["VITE_SERVER_URL", "/api"],
21247
+ {{/if}}
21248
+ ]);
21249
+
21250
+ const args = process.argv.slice(2);
21251
+ const separatorIndex = args.indexOf("--");
21252
+ const scriptArgs = separatorIndex === -1 ? args : args.slice(0, separatorIndex);
21253
+ const forwardedArgs = separatorIndex === -1 ? [] : args.slice(separatorIndex + 1);
21254
+
21255
+ const environment =
21256
+ scriptArgs[0] && VALID_ENVIRONMENTS.has(scriptArgs[0]) ? scriptArgs[0] : DEFAULT_ENVIRONMENT;
21257
+ const remainingArgs = scriptArgs.slice(VALID_ENVIRONMENTS.has(scriptArgs[0] ?? "") ? 1 : 0);
21258
+ // Split remaining args into env-file paths and passthrough Vercel CLI flags.
21259
+ // A bare token counts as a file only when it exists on disk, so flags and their
21260
+ // values (e.g. \`--scope my-team\`) forward correctly regardless of argument order.
21261
+ const files: string[] = [];
21262
+ const passthroughArgs: string[] = [];
21263
+ for (const arg of remainingArgs) {
21264
+ if (!arg.startsWith("-") && existsSync(arg)) {
21265
+ files.push(arg);
21266
+ } else {
21267
+ passthroughArgs.push(arg);
21268
+ }
21269
+ }
21270
+ const vercelArgs = [...passthroughArgs, ...forwardedArgs];
21271
+ const envFiles = files.length > 0 ? files : DEFAULT_FILES;
21272
+
21273
+ if (envFiles.length === 0) {
21274
+ console.log("No env files configured for this Vercel stack.");
21275
+ process.exit(0);
21276
+ }
21277
+
21278
+ const env = new Map<string, string>();
21279
+
21280
+ for (const file of envFiles) {
21281
+ if (!existsSync(file)) {
21282
+ console.warn(\`Skipping missing env file: \${file}\`);
21283
+ continue;
21284
+ }
21285
+
21286
+ for (const [key, value] of Object.entries(dotenv.parse(readFileSync(file, "utf8")))) {
21287
+ if (SKIP_KEYS.has(key)) continue;
21288
+ env.set(key, OVERRIDE_KEYS.get(key) ?? value);
21289
+ }
21290
+ }
21291
+
21292
+ if (env.size === 0) {
21293
+ console.log("No Vercel env vars found to sync.");
21294
+ process.exit(0);
21295
+ }
21296
+
21297
+ const LOCAL_VALUE_PATTERN = /localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|^file:/i;
21298
+ const localKeys = [...env.entries()]
21299
+ .filter(([, value]) => LOCAL_VALUE_PATTERN.test(value))
21300
+ .map(([key]) => key);
21301
+ if (localKeys.length > 0) {
21302
+ console.warn(
21303
+ \`Warning: \${localKeys.join(", ")} look\${localKeys.length === 1 ? "s" : ""} like local-only value(s). Update them in your .env file(s) and re-run this sync if your deployed app should not point at local endpoints.\`,
21304
+ );
21305
+ }
21306
+
21307
+ console.log(\`Syncing \${env.size} env var(s) to Vercel \${environment}.\`);
21308
+ for (const [key, value] of env.entries()) {
21309
+ const result = spawnSync(
21310
+ VERCEL_COMMAND[0],
21311
+ [
21312
+ ...VERCEL_COMMAND.slice(1),
21313
+ "env",
21314
+ "add",
21315
+ key,
21316
+ environment,
21317
+ "--force",
21318
+ "--yes",
21319
+ "--non-interactive",
21320
+ ...vercelArgs,
21321
+ ],
21322
+ {
21323
+ input: \`\${value}\\n\`,
21324
+ stdio: ["pipe", "inherit", "inherit"],
21325
+ encoding: "utf8",
21326
+ // Windows resolves bunx/npx/pnpm via .cmd shims, which need a shell
21327
+ shell: process.platform === "win32",
21328
+ },
21329
+ );
21330
+
21331
+ if (result.error) {
21332
+ console.error(\`Failed to sync \${key}: \${result.error.message}\`);
21333
+ process.exit(1);
21334
+ }
21335
+
21336
+ if (result.status !== 0) {
21337
+ console.error(\`Failed to sync \${key}\`);
21338
+ process.exit(result.status ?? 1);
21339
+ }
21340
+ }
21341
+
21342
+ console.log("Vercel env sync complete. Redeploy for changes to take effect.");
20920
21343
  `],
20921
21344
  ["examples/ai/convex/packages/backend/convex/agent.ts.hbs", `import { Agent } from "@convex-dev/agent";
20922
21345
  import { google } from "@ai-sdk/google";
@@ -29460,12 +29883,12 @@ function TodosRoute() {
29460
29883
  let newTodoText = $state('');
29461
29884
 
29462
29885
  {{#if (eq api "orpc")}}
29463
- const todosQuery = createQuery(orpc.todo.getAll.queryOptions());
29886
+ const todosQuery = createQuery(() => orpc.todo.getAll.queryOptions());
29464
29887
 
29465
- const addMutation = createMutation(
29888
+ const addMutation = createMutation(() =>
29466
29889
  orpc.todo.create.mutationOptions({
29467
29890
  onSuccess: () => {
29468
- $todosQuery.refetch();
29891
+ todosQuery.refetch();
29469
29892
  newTodoText = '';
29470
29893
  },
29471
29894
  onError: (error) => {
@@ -29474,10 +29897,10 @@ function TodosRoute() {
29474
29897
  })
29475
29898
  );
29476
29899
 
29477
- const toggleMutation = createMutation(
29900
+ const toggleMutation = createMutation(() =>
29478
29901
  orpc.todo.toggle.mutationOptions({
29479
29902
  onSuccess: () => {
29480
- $todosQuery.refetch();
29903
+ todosQuery.refetch();
29481
29904
  },
29482
29905
  onError: (error) => {
29483
29906
  console.error('Failed to toggle todo:', error?.message ?? error);
@@ -29485,10 +29908,10 @@ function TodosRoute() {
29485
29908
  })
29486
29909
  );
29487
29910
 
29488
- const deleteMutation = createMutation(
29911
+ const deleteMutation = createMutation(() =>
29489
29912
  orpc.todo.delete.mutationOptions({
29490
29913
  onSuccess: () => {
29491
- $todosQuery.refetch();
29914
+ todosQuery.refetch();
29492
29915
  },
29493
29916
  onError: (error) => {
29494
29917
  console.error('Failed to delete todo:', error?.message ?? error);
@@ -29501,22 +29924,22 @@ function TodosRoute() {
29501
29924
  event.preventDefault();
29502
29925
  const text = newTodoText.trim();
29503
29926
  if (text) {
29504
- $addMutation.mutate({ text });
29927
+ addMutation.mutate({ text });
29505
29928
  }
29506
29929
  }
29507
29930
 
29508
29931
  function handleToggleTodo(id: number, completed: boolean) {
29509
- $toggleMutation.mutate({ id, completed: !completed });
29932
+ toggleMutation.mutate({ id, completed: !completed });
29510
29933
  }
29511
29934
 
29512
29935
  function handleDeleteTodo(id: number) {
29513
- $deleteMutation.mutate({ id });
29936
+ deleteMutation.mutate({ id });
29514
29937
  }
29515
29938
 
29516
- const isAdding = $derived($addMutation.isPending);
29939
+ const isAdding = $derived(addMutation.isPending);
29517
29940
  const canAdd = $derived(!isAdding && newTodoText.trim().length > 0);
29518
- const isLoadingTodos = $derived($todosQuery.isLoading);
29519
- const todos = $derived($todosQuery.data ?? []);
29941
+ const isLoadingTodos = $derived(todosQuery.isLoading);
29942
+ const todos = $derived(todosQuery.data ?? []);
29520
29943
  const hasTodos = $derived(todos.length > 0);
29521
29944
 
29522
29945
  <\/script>
@@ -29548,8 +29971,8 @@ function TodosRoute() {
29548
29971
  {:else}
29549
29972
  <ul class="space-y-1">
29550
29973
  {#each todos as todo (todo.id)}
29551
- {@const isToggling = $toggleMutation.isPending && $toggleMutation.variables?.id === todo.id}
29552
- {@const isDeleting = $deleteMutation.isPending && $deleteMutation.variables?.id === todo.id}
29974
+ {@const isToggling = toggleMutation.isPending && toggleMutation.variables?.id === todo.id}
29975
+ {@const isDeleting = deleteMutation.isPending && deleteMutation.variables?.id === todo.id}
29553
29976
  {@const isDisabled = isToggling || isDeleting}
29554
29977
  <li
29555
29978
  class="flex items-center justify-between p-2 "
@@ -29584,24 +30007,24 @@ function TodosRoute() {
29584
30007
  </ul>
29585
30008
  {/if}
29586
30009
 
29587
- {#if $todosQuery.isError}
30010
+ {#if todosQuery.isError}
29588
30011
  <p class="mt-4 text-red-500">
29589
- Error loading: {$todosQuery.error?.message ?? 'Unknown error'}
30012
+ Error loading: {todosQuery.error?.message ?? 'Unknown error'}
29590
30013
  </p>
29591
30014
  {/if}
29592
- {#if $addMutation.isError}
30015
+ {#if addMutation.isError}
29593
30016
  <p class="mt-4 text-red-500">
29594
- Error adding: {$addMutation.error?.message ?? 'Unknown error'}
30017
+ Error adding: {addMutation.error?.message ?? 'Unknown error'}
29595
30018
  </p>
29596
30019
  {/if}
29597
- {#if $toggleMutation.isError}
30020
+ {#if toggleMutation.isError}
29598
30021
  <p class="mt-4 text-red-500">
29599
- Error updating: {$toggleMutation.error?.message ?? 'Unknown error'}
30022
+ Error updating: {toggleMutation.error?.message ?? 'Unknown error'}
29600
30023
  </p>
29601
30024
  {/if}
29602
- {#if $deleteMutation.isError}
30025
+ {#if deleteMutation.isError}
29603
30026
  <p class="mt-4 text-red-500">
29604
- Error deleting: {$deleteMutation.error?.message ?? 'Unknown error'}
30027
+ Error deleting: {deleteMutation.error?.message ?? 'Unknown error'}
29605
30028
  </p>
29606
30029
  {/if}
29607
30030
  </div>
@@ -29613,14 +30036,7 @@ shamefully-hoist=true
29613
30036
  strict-peer-dependencies=false
29614
30037
  {{/if}}`],
29615
30038
  ["extras/bunfig.toml.hbs", `[install]
29616
- {{#if (includes frontend "nuxt")}}
29617
- linker = "hoisted" # Nuxt needs hoisting for its dependency resolver
29618
- {{else}}
29619
- linker = "isolated"
29620
- {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
29621
30039
  peer = false # Expo native projects declare SDK peers explicitly; this keeps Bun isolated installs deduped for native modules
29622
- {{/if}}
29623
- {{/if}}
29624
30040
  `],
29625
30041
  ["extras/env.d.ts.hbs", `{{#if (eq serverDeploy "cloudflare")}}
29626
30042
  import { type server } from "@{{projectName}}/infra/alchemy.run";
@@ -29646,19 +30062,19 @@ declare module "cloudflare:workers" {
29646
30062
  ["extras/pnpm-workspace.yaml.hbs", `packages:
29647
30063
  - "apps/*"
29648
30064
  - "packages/*"
29649
- {{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (eq serverDeploy "docker") (eq orm "prisma") (includes addons "lefthook") (includes addons "nx") (includes addons "pwa") (includes addons "turborepo") (includes addons "vite-plus") (includes frontend "react-router") (includes frontend "next") (includes frontend "nuxt"))}}
30065
+ {{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (eq serverDeploy "docker") (eq webDeploy "vercel") (eq serverDeploy "vercel") (eq orm "prisma") (includes addons "lefthook") (includes addons "nx") (includes addons "pwa") (includes addons "turborepo") (includes addons "vite-plus") (includes frontend "react-router") (includes frontend "next") (includes frontend "nuxt"))}}
29650
30066
 
29651
30067
  # pnpm 11 blocks dependency lifecycle scripts unless they are approved here.
29652
30068
  # Entries are scoped to packages this generated stack can pull in.
29653
30069
  allowBuilds:
29654
- {{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (eq serverDeploy "docker") (includes addons "turborepo") (includes addons "vite-plus") (includes frontend "react-router") (includes frontend "nuxt"))}}
30070
+ {{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (eq serverDeploy "docker") (eq webDeploy "vercel") (eq serverDeploy "vercel") (includes addons "turborepo") (includes addons "vite-plus") (includes frontend "react-router") (includes frontend "nuxt"))}}
29655
30071
  esbuild: true
29656
30072
  {{/if}}
29657
30073
  {{#if (includes frontend "nuxt")}}
29658
30074
  "@parcel/watcher": true
29659
30075
  vue-demi: true
29660
30076
  {{/if}}
29661
- {{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (includes addons "pwa") (includes frontend "next"))}}
30077
+ {{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (eq webDeploy "vercel") (includes addons "pwa") (includes frontend "next"))}}
29662
30078
  sharp: true
29663
30079
  {{/if}}
29664
30080
  {{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare"))}}
@@ -29705,6 +30121,8 @@ pnpm-debug.log*
29705
30121
  import tailwindcss from "@tailwindcss/vite";
29706
30122
  import { defineConfig, envField } from "astro/config";
29707
30123
  {{#if (or (includes addons "electrobun") (includes addons "tauri"))}}
30124
+ {{else if (eq webDeploy "vercel")}}
30125
+ import vercel from "@astrojs/vercel";
29708
30126
  {{else}}
29709
30127
  import node from "@astrojs/node";
29710
30128
  {{/if}}
@@ -29713,6 +30131,9 @@ import node from "@astrojs/node";
29713
30131
  export default defineConfig({
29714
30132
  {{#if (or (includes addons "electrobun") (includes addons "tauri"))}}
29715
30133
  output: "static",
30134
+ {{else if (eq webDeploy "vercel")}}
30135
+ output: "server",
30136
+ adapter: vercel(),
29716
30137
  {{else}}
29717
30138
  output: "server",
29718
30139
  adapter: node({ mode: "standalone" }),
@@ -29745,11 +30166,11 @@ export default defineConfig({
29745
30166
  "astro": "astro"
29746
30167
  },
29747
30168
  "dependencies": {
29748
- "astro": "^7.0.3"
30169
+ "astro": "^7.0.5"
29749
30170
  },
29750
30171
  "devDependencies": {
29751
- "@tailwindcss/vite": "^4.3.1",
29752
- "tailwindcss": "^4.3.1"
30172
+ "@tailwindcss/vite": "^4.3.2",
30173
+ "tailwindcss": "^4.3.2"
29753
30174
  }
29754
30175
  }
29755
30176
  `],
@@ -29873,9 +30294,6 @@ const { title = "{{projectName}}" } = Astro.props;
29873
30294
  `],
29874
30295
  ["frontend/astro/src/pages/index.astro.hbs", `---
29875
30296
  import Layout from "../layouts/Layout.astro";
29876
- {{#if (eq api "orpc")}}
29877
- import { orpc } from "../lib/orpc";
29878
- {{/if}}
29879
30297
 
29880
30298
  const TITLE_TEXT = \`
29881
30299
  ██████╗ ███████╗████████╗████████╗███████╗██████╗
@@ -33582,7 +34000,7 @@ module.exports = uniwindConfig;
33582
34000
  "expo-secure-store": "~57.0.0",
33583
34001
  "expo-status-bar": "~57.0.0",
33584
34002
  "expo-web-browser": "~57.0.0",
33585
- "heroui-native": "^1.0.4",
34003
+ "heroui-native": "^1.0.5",
33586
34004
  "react": "19.2.3",
33587
34005
  "react-dom": "19.2.3",
33588
34006
  "react-native": "0.86.0",
@@ -33597,7 +34015,7 @@ module.exports = uniwindConfig;
33597
34015
  "tailwind-merge": "^3.6.0",
33598
34016
  "tailwind-variants": "^3.2.2",
33599
34017
  "tailwindcss": "^4.3.2",
33600
- "uniwind": "^1.9.0"
34018
+ "uniwind": "^1.10.0"
33601
34019
  },
33602
34020
  "devDependencies": {
33603
34021
  "@types/node": "^26.0.1",
@@ -33887,13 +34305,13 @@ export default defineNuxtConfig({
33887
34305
  "postinstall": "nuxt prepare"
33888
34306
  },
33889
34307
  "dependencies": {
33890
- "@nuxt/ui": "^4.5.1",
33891
- "nuxt": "^4.4.4",
33892
- "vue": "^3.5.38"
34308
+ "@nuxt/ui": "^4.9.0",
34309
+ "nuxt": "^4.4.8",
34310
+ "vue": "^3.5.39"
33893
34311
  },
33894
34312
  "devDependencies": {
33895
- "tailwindcss": "^4.3.1",
33896
- "@iconify-json/lucide": "^1.2.96"
34313
+ "tailwindcss": "^4.3.2",
34314
+ "@iconify-json/lucide": "^1.2.115"
33897
34315
  }
33898
34316
  }
33899
34317
  `],
@@ -33970,7 +34388,7 @@ initOpenNextCloudflareForDev();
33970
34388
  "dependencies": {
33971
34389
  "@{{projectName}}/ui": "{{#if (eq packageManager "npm")}}*{{else}}workspace:*{{/if}}",
33972
34390
  "@swc/helpers": "^0.5.23",
33973
- "lucide-react": "^1.21.0",
34391
+ "lucide-react": "^1.23.0",
33974
34392
  "next": "^16.2.0",
33975
34393
  "next-themes": "^0.4.6",
33976
34394
  "react": "^19.2.7",
@@ -33979,11 +34397,11 @@ initOpenNextCloudflareForDev();
33979
34397
  "babel-plugin-react-compiler": "^1.0.0"
33980
34398
  },
33981
34399
  "devDependencies": {
33982
- "@tailwindcss/postcss": "^4.3.1",
34400
+ "@tailwindcss/postcss": "^4.3.2",
33983
34401
  "@types/node": "^20",
33984
34402
  "@types/react": "^19.2.17",
33985
34403
  "@types/react-dom": "^19.2.3",
33986
- "tailwindcss": "^4.3.1"
34404
+ "tailwindcss": "^4.3.2"
33987
34405
  }
33988
34406
  }
33989
34407
  `],
@@ -34372,39 +34790,42 @@ export function ThemeProvider({
34372
34790
  },
34373
34791
  "dependencies": {
34374
34792
  "@{{projectName}}/ui": "{{#if (eq packageManager "npm")}}*{{else}}workspace:*{{/if}}",
34375
- "@react-router/fs-routes": "^7.14.1",
34376
- "@react-router/node": "^7.14.1",
34377
- "@react-router/serve": "^7.14.1",
34378
- "isbot": "^5.1.39",
34379
- "lucide-react": "^1.21.0",
34793
+ "@react-router/fs-routes": "^8.1.0",
34794
+ "@react-router/node": "^8.1.0",
34795
+ "@react-router/serve": "^8.1.0",
34796
+ "isbot": "^5.1.44",
34797
+ "lucide-react": "^1.23.0",
34380
34798
  "next-themes": "^0.4.6",
34381
34799
  "react": "^19.2.7",
34382
34800
  "react-dom": "^19.2.7",
34383
- "react-router": "^7.14.1",
34801
+ "react-router": "^8.1.0",
34384
34802
  "sonner": "^2.0.7"
34385
34803
  },
34386
34804
  "devDependencies": {
34387
- "@react-router/dev": "^7.14.1",
34388
- "@tailwindcss/vite": "^4.3.1",
34389
- "@types/node": "^20",
34805
+ "@react-router/dev": "^8.1.0",
34806
+ "@tailwindcss/vite": "^4.3.2",
34807
+ "@types/node": "^22.13.14",
34390
34808
  "@types/react": "^19.2.17",
34391
34809
  "@types/react-dom": "^19.2.3",
34392
- "react-router-devtools": "^1.1.0",
34393
- "tailwindcss": "^4.3.1",
34394
- "vite": "^8.0.8",
34810
+ "react-router-devtools": "^6.2.1",
34811
+ "tailwindcss": "^4.3.2",
34812
+ "vite": "^7.3.6",
34395
34813
  "vite-tsconfig-paths": "^6.1.1"
34396
34814
  }
34397
34815
  }
34398
34816
  `],
34399
34817
  ["frontend/react/react-router/public/favicon.ico", `[Binary file]`],
34400
- ["frontend/react/react-router/react-router.config.ts", `import type { Config } from "@react-router/dev/config";
34818
+ ["frontend/react/react-router/react-router.config.ts.hbs", `import type { Config } from "@react-router/dev/config";
34401
34819
 
34402
34820
  export default {
34821
+ {{#if (and (eq webDeploy "vercel") (not (or (includes addons "tauri") (includes addons "electrobun"))))}}
34822
+ // Adopt vercelPreset() once @vercel/react-router supports RR8 (vercel/vercel#16730)
34823
+ {{/if}}
34824
+ {{#if (or (includes addons "tauri") (includes addons "electrobun"))}}
34825
+ // Desktop addons package static web assets; SSR output cannot be bundled
34403
34826
  ssr: false,
34827
+ {{/if}}
34404
34828
  appDirectory: "src",
34405
- future: {
34406
- v8_middleware: true,
34407
- },
34408
34829
  } satisfies Config;
34409
34830
  `],
34410
34831
  ["frontend/react/react-router/src/components/mode-toggle.tsx.hbs", `import { Moon, Sun } from "lucide-react";
@@ -34881,6 +35302,12 @@ export default defineConfig({
34881
35302
  reactRouter(),
34882
35303
  tsconfigPaths(),
34883
35304
  ],
35305
+ {{#if (and (eq webDeploy "vercel") (not (or (includes addons "tauri") (includes addons "electrobun"))))}}
35306
+ ssr: {
35307
+ // Vercel functions have no node_modules; bundle all deps into the server build
35308
+ noExternal: true,
35309
+ },
35310
+ {{/if}}
34884
35311
  });
34885
35312
  `],
34886
35313
  ["frontend/react/tanstack-router/index.html.hbs", `<!DOCTYPE html>
@@ -34910,26 +35337,26 @@ export default defineConfig({
34910
35337
  "check-types": "vite build && tsc --noEmit"
34911
35338
  },
34912
35339
  "dependencies": {
34913
- "@hookform/resolvers": "^5.2.2",
35340
+ "@hookform/resolvers": "^5.4.0",
34914
35341
  "@{{projectName}}/ui": "{{#if (eq packageManager "npm")}}*{{else}}workspace:*{{/if}}",
34915
- "@tailwindcss/vite": "^4.3.1",
34916
- "@tanstack/react-router": "^1.168.22",
34917
- "lucide-react": "^1.21.0",
35342
+ "@tailwindcss/vite": "^4.3.2",
35343
+ "@tanstack/react-router": "^1.170.17",
35344
+ "lucide-react": "^1.23.0",
34918
35345
  "next-themes": "^0.4.6",
34919
35346
  "react": "^19.2.7",
34920
35347
  "react-dom": "^19.2.7",
34921
35348
  "sonner": "^2.0.7"
34922
35349
  },
34923
35350
  "devDependencies": {
34924
- "@tanstack/react-router-devtools": "^1.166.13",
34925
- "@tanstack/router-plugin": "^1.167.22",
35351
+ "@tanstack/react-router-devtools": "^1.167.0",
35352
+ "@tanstack/router-plugin": "^1.168.19",
34926
35353
  "@types/node": "^22.13.14",
34927
35354
  "@types/react": "^19.2.17",
34928
35355
  "@types/react-dom": "^19.2.3",
34929
- "@vitejs/plugin-react": "^6.0.1",
34930
- "postcss": "^8.5.10",
34931
- "tailwindcss": "^4.3.1",
34932
- "vite": "^8.0.8"
35356
+ "@vitejs/plugin-react": "^6.0.3",
35357
+ "postcss": "^8.5.16",
35358
+ "tailwindcss": "^4.3.2",
35359
+ "vite": "^8.1.3"
34933
35360
  }
34934
35361
  }
34935
35362
  `],
@@ -35355,27 +35782,27 @@ export default defineConfig({
35355
35782
  },
35356
35783
  "dependencies": {
35357
35784
  "@{{projectName}}/ui": "{{#if (eq packageManager "npm")}}*{{else}}workspace:*{{/if}}",
35358
- "@tailwindcss/vite": "^4.3.1",
35359
- "@tanstack/react-query": "^5.99.0",
35360
- "@tanstack/react-router": "^1.168.22",
35361
- "@tanstack/react-start": "^1.167.41",
35362
- "lucide-react": "^1.21.0",
35785
+ "@tailwindcss/vite": "^4.3.2",
35786
+ "@tanstack/react-query": "^5.101.2",
35787
+ "@tanstack/react-router": "^1.170.17",
35788
+ "@tanstack/react-start": "^1.168.27",
35789
+ "lucide-react": "^1.23.0",
35363
35790
  "next-themes": "^0.4.6",
35364
35791
  "react": "^19.2.7",
35365
35792
  "react-dom": "^19.2.7",
35366
35793
  "sonner": "^2.0.7",
35367
- "tailwindcss": "^4.3.1"
35794
+ "tailwindcss": "^4.3.2"
35368
35795
  },
35369
35796
  "devDependencies": {
35370
- "@tanstack/react-router-devtools": "^1.166.13",
35797
+ "@tanstack/react-router-devtools": "^1.167.0",
35371
35798
  "@testing-library/dom": "^10.4.1",
35372
35799
  "@testing-library/react": "^16.3.2",
35373
35800
  "@types/react": "^19.2.17",
35374
35801
  "@types/react-dom": "^19.2.3",
35375
- "@vitejs/plugin-react": "^6.0.1",
35376
- "jsdom": "^29.0.2",
35377
- "vite": "^8.0.8",
35378
- "web-vitals": "^5.2.0"
35802
+ "@vitejs/plugin-react": "^6.0.3",
35803
+ "jsdom": "^29.1.1",
35804
+ "vite": "^8.1.3",
35805
+ "web-vitals": "^5.3.0"
35379
35806
  }
35380
35807
  }
35381
35808
  `],
@@ -35451,6 +35878,10 @@ export function getRouter() {
35451
35878
  }
35452
35879
  {{else}}
35453
35880
  {{#if (eq api "trpc")}}
35881
+ {{#unless (eq backend "self")}}
35882
+ {{> getServerUrl}}
35883
+
35884
+ {{/unless}}
35454
35885
  function createQueryClient() {
35455
35886
  return new QueryClient({
35456
35887
  queryCache: new QueryCache({
@@ -35472,7 +35903,7 @@ function createQueryClient() {
35472
35903
  const trpcClient = createTRPCClient<AppRouter>({
35473
35904
  links: [
35474
35905
  httpBatchLink({
35475
- url: {{#if (eq backend "self")}}"/api/trpc"{{else}}\`\${env.VITE_SERVER_URL}/trpc\`{{/if}},
35906
+ url: {{#if (eq backend "self")}}"/api/trpc"{{else}}\`\${getServerUrl(env.VITE_SERVER_URL)}/trpc\`{{/if}},
35476
35907
  {{#if (eq auth "clerk")}}
35477
35908
  headers: async () => {
35478
35909
  const token = await getClerkAuthToken();
@@ -35915,7 +36346,7 @@ function HomeComponent() {
35915
36346
  `],
35916
36347
  ["frontend/react/tanstack-start/vite.config.ts.hbs", `import { defineConfig } from "{{#if (includes addons "vite-plus")}}vite-plus{{else}}vite{{/if}}";
35917
36348
  import { tanstackStart } from "@tanstack/react-start/plugin/vite";
35918
- {{#if (eq webDeploy "docker")}}
36349
+ {{#if (or (eq webDeploy "docker") (eq webDeploy "vercel"))}}
35919
36350
  import { nitro } from "nitro/vite";
35920
36351
  {{/if}}
35921
36352
  import tailwindcss from "@tailwindcss/vite";
@@ -35937,12 +36368,17 @@ export default defineConfig({
35937
36368
  },
35938
36369
  },
35939
36370
  {{/if}}),
35940
- {{#if (eq webDeploy "docker")}}
36371
+ {{#if (or (eq webDeploy "docker") (eq webDeploy "vercel"))}}
35941
36372
  nitro(),
35942
36373
  {{/if}}
35943
36374
  viteReact(),
35944
36375
  ],
35945
- {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
36376
+ {{#if (eq webDeploy "vercel")}}
36377
+ // Bundle all SSR deps: Vercel functions have no node_modules at runtime
36378
+ ssr: {
36379
+ noExternal: true,
36380
+ },
36381
+ {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
35946
36382
  ssr: {
35947
36383
  noExternal: ["@convex-dev/better-auth"],
35948
36384
  },
@@ -36165,16 +36601,16 @@ dist-ssr
36165
36601
  "test": "vitest run"
36166
36602
  },
36167
36603
  "dependencies": {
36168
- "@tailwindcss/vite": "^4.3.1",
36169
- "@tanstack/router-plugin": "^1.167.22",
36170
- "@tanstack/solid-router": "^1.168.20",
36171
- "lucide-solid": "^1.8.0",
36172
- "solid-js": "^1.9.12",
36173
- "tailwindcss": "^4.3.1"
36604
+ "@tailwindcss/vite": "^4.3.2",
36605
+ "@tanstack/router-plugin": "^1.168.19",
36606
+ "@tanstack/solid-router": "^1.170.17",
36607
+ "lucide-solid": "^1.23.0",
36608
+ "solid-js": "^1.9.14",
36609
+ "tailwindcss": "^4.3.2"
36174
36610
  },
36175
36611
  "devDependencies": {
36176
- "@tanstack/solid-router-devtools": "^1.166.13",
36177
- "vite": "^8.0.8",
36612
+ "@tanstack/solid-router-devtools": "^1.167.0",
36613
+ "vite": "^8.1.3",
36178
36614
  "vite-plugin-solid": "^2.11.12"
36179
36615
  }
36180
36616
  }
@@ -36485,13 +36921,13 @@ vite.config.ts.timestamp-*
36485
36921
  {{else}}
36486
36922
  "@sveltejs/adapter-auto": "^7.0.1",
36487
36923
  {{/if}}
36488
- "@sveltejs/kit": "^2.58.0",
36489
- "@sveltejs/vite-plugin-svelte": "^7.0.0",
36490
- "@tailwindcss/vite": "^4.3.1",
36491
- "svelte": "^5.55.5",
36492
- "svelte-check": "^4.4.6",
36493
- "tailwindcss": "^4.3.1",
36494
- "vite": "^8.0.10"
36924
+ "@sveltejs/kit": "^2.69.0",
36925
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
36926
+ "@tailwindcss/vite": "^4.3.2",
36927
+ "svelte": "^5.56.4",
36928
+ "svelte-check": "^4.7.1",
36929
+ "tailwindcss": "^4.3.2",
36930
+ "vite": "^8.1.3"
36495
36931
  },
36496
36932
  "dependencies": {}
36497
36933
  }
@@ -36687,7 +37123,7 @@ const TITLE_TEXT = \`
36687
37123
  {{#if (eq api "orpc")}}
36688
37124
  import { orpc } from "$lib/orpc";
36689
37125
  import { createQuery } from "@tanstack/svelte-query";
36690
- const healthCheck = createQuery(orpc.healthCheck.queryOptions());
37126
+ const healthCheck = createQuery(() => orpc.healthCheck.queryOptions());
36691
37127
  {{/if}}
36692
37128
 
36693
37129
  const TITLE_TEXT = \`
@@ -36715,12 +37151,12 @@ const TITLE_TEXT = \`
36715
37151
  <h2 class="mb-2 font-medium">API Status</h2>
36716
37152
  <div class="flex items-center gap-2">
36717
37153
  <div
36718
- class={\`h-2 w-2 rounded-full \${$healthCheck.data ? "bg-green-500" : "bg-red-500"}\`}
37154
+ class={\`h-2 w-2 rounded-full \${healthCheck.data ? "bg-green-500" : "bg-red-500"}\`}
36719
37155
  ></div>
36720
37156
  <span class="text-muted-foreground text-sm">
36721
- {$healthCheck.isLoading
37157
+ {healthCheck.isLoading
36722
37158
  ? "Checking..."
36723
- : $healthCheck.data
37159
+ : healthCheck.data
36724
37160
  ? "Connected"
36725
37161
  : "Disconnected"}
36726
37162
  </span>
@@ -36738,6 +37174,8 @@ import adapter from '@sveltejs/adapter-static';
36738
37174
  import alchemy from 'alchemy/cloudflare/sveltekit';
36739
37175
  {{else if (eq webDeploy "docker")}}
36740
37176
  import adapter from '@sveltejs/adapter-node';
37177
+ {{else if (eq webDeploy "vercel")}}
37178
+ import adapter from '@sveltejs/adapter-vercel';
36741
37179
  {{else}}
36742
37180
  import adapter from '@sveltejs/adapter-auto';
36743
37181
  {{/if}}
@@ -36763,6 +37201,8 @@ const config = {
36763
37201
  {{else if (eq webDeploy "docker")}}
36764
37202
  // adapter-node builds a standalone Node server (run with \`node build/index.js\`).
36765
37203
  adapter: adapter()
37204
+ {{else if (eq webDeploy "vercel")}}
37205
+ adapter: adapter()
36766
37206
  {{else}}
36767
37207
  // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
36768
37208
  // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
@@ -36993,6 +37433,33 @@ import "dotenv/config";
36993
37433
  import { createEnv } from "@t3-oss/env-core";
36994
37434
  import { z } from "zod";
36995
37435
 
37436
+ {{#if (or (eq webDeploy "vercel") (eq serverDeploy "vercel"))}}
37437
+ function getVercelOrigin() {
37438
+ const vercelUrl =
37439
+ process.env.VERCEL_ENV === "production"
37440
+ ? (process.env.VERCEL_PROJECT_PRODUCTION_URL ?? process.env.VERCEL_URL)
37441
+ : (process.env.VERCEL_URL ?? process.env.VERCEL_PROJECT_PRODUCTION_URL);
37442
+ if (!vercelUrl) return undefined;
37443
+ return vercelUrl.startsWith("http") ? vercelUrl : \`https://\${vercelUrl}\`;
37444
+ }
37445
+
37446
+ const vercelOrigin = getVercelOrigin();
37447
+
37448
+ const runtimeEnv = {
37449
+ ...process.env,
37450
+ {{#if (eq auth "better-auth")}}
37451
+ {{#if (and (eq webDeploy "vercel") (eq serverDeploy "vercel") (ne backend "self"))}}
37452
+ // Public auth base: /api/auth bypasses the rewrite's path strip, so the
37453
+ // same URL works for incoming matching and generated callbacks
37454
+ BETTER_AUTH_URL: process.env.BETTER_AUTH_URL ?? (vercelOrigin ? \`\${vercelOrigin}/api/auth\` : undefined),
37455
+ {{else}}
37456
+ BETTER_AUTH_URL: process.env.BETTER_AUTH_URL ?? vercelOrigin,
37457
+ {{/if}}
37458
+ {{/if}}
37459
+ CORS_ORIGIN: process.env.CORS_ORIGIN ?? vercelOrigin,
37460
+ };
37461
+
37462
+ {{/if}}
36996
37463
  export const env = createEnv({
36997
37464
  server: {
36998
37465
  {{#if (ne database "none")}}
@@ -37024,7 +37491,7 @@ export const env = createEnv({
37024
37491
  CORS_ORIGIN: z.url(),
37025
37492
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
37026
37493
  },
37027
- runtimeEnv: process.env,
37494
+ runtimeEnv: {{#if (or (eq webDeploy "vercel") (eq serverDeploy "vercel"))}}runtimeEnv{{else}}process.env{{/if}},
37028
37495
  skipValidation: !!process.env.SKIP_ENV_VALIDATION,
37029
37496
  emptyStringAsUndefined: true,
37030
37497
  });
@@ -37041,6 +37508,13 @@ import { createEnv } from "@t3-oss/env-core";
37041
37508
  {{/if}}
37042
37509
  import { z } from "zod";
37043
37510
 
37511
+ {{#if (and (eq webDeploy "vercel") (eq serverDeploy "vercel") (ne backend "self") (ne backend "none") (ne backend "convex"))}}
37512
+ const serverUrlSchema = z.union([
37513
+ z.url(),
37514
+ z.string().regex(/^\\/(?!\\/)/, "Use an absolute URL or a same-origin path like /api"),
37515
+ ]);
37516
+
37517
+ {{/if}}
37044
37518
  {{#if (eq backend "convex")}}
37045
37519
  const convexUrlSchema = (exampleHost: string) =>
37046
37520
  z.url().refine((url) => new URL(url).hostname !== exampleHost, {
@@ -37126,7 +37600,7 @@ export const env = createEnv({
37126
37600
  {{else if (ne backend "none")}}
37127
37601
  {{#if (includes frontend "next")}}
37128
37602
  client: {
37129
- NEXT_PUBLIC_SERVER_URL: z.url(),
37603
+ NEXT_PUBLIC_SERVER_URL: {{#if (and (eq webDeploy "vercel") (eq serverDeploy "vercel"))}}serverUrlSchema{{else}}z.url(){{/if}},
37130
37604
  {{#if (eq auth "clerk")}}
37131
37605
  NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
37132
37606
  {{/if}}
@@ -37139,18 +37613,18 @@ export const env = createEnv({
37139
37613
  },
37140
37614
  {{else if (includes frontend "nuxt")}}
37141
37615
  client: {
37142
- NUXT_PUBLIC_SERVER_URL: z.url(),
37616
+ NUXT_PUBLIC_SERVER_URL: {{#if (and (eq webDeploy "vercel") (eq serverDeploy "vercel"))}}serverUrlSchema{{else}}z.url(){{/if}},
37143
37617
  },
37144
37618
  {{else if (or (includes frontend "svelte") (includes frontend "astro"))}}
37145
37619
  clientPrefix: "PUBLIC_",
37146
37620
  client: {
37147
- PUBLIC_SERVER_URL: z.url(),
37621
+ PUBLIC_SERVER_URL: {{#if (and (eq webDeploy "vercel") (eq serverDeploy "vercel"))}}serverUrlSchema{{else}}z.url(){{/if}},
37148
37622
  },
37149
37623
  runtimeEnv: (import.meta as any).env,
37150
37624
  {{else}}
37151
37625
  clientPrefix: "VITE_",
37152
37626
  client: {
37153
- VITE_SERVER_URL: z.url(),
37627
+ VITE_SERVER_URL: {{#if (and (eq webDeploy "vercel") (eq serverDeploy "vercel"))}}serverUrlSchema{{else}}z.url(){{/if}},
37154
37628
  {{#if (eq auth "clerk")}}
37155
37629
  VITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),
37156
37630
  {{/if}}
@@ -37646,11 +38120,11 @@ await app.finalize();
37646
38120
  },
37647
38121
  "dependencies": {
37648
38122
  "@base-ui/react": "^1.6.0",
37649
- "@shadcn/react": "^0.1.0",
38123
+ "@shadcn/react": "^0.2.0",
37650
38124
  "shadcn": "^4.12.0",
37651
38125
  "class-variance-authority": "^0.7.1",
37652
38126
  "clsx": "^2.1.1",
37653
- "lucide-react": "^1.21.0",
38127
+ "lucide-react": "^1.23.0",
37654
38128
  "next-themes": "^0.4.6",
37655
38129
  "react": "^19.2.7",
37656
38130
  "react-dom": "^19.2.7",
@@ -37659,10 +38133,10 @@ await app.finalize();
37659
38133
  "tw-animate-css": "^1.4.0"
37660
38134
  },
37661
38135
  "devDependencies": {
37662
- "@tailwindcss/postcss": "^4.3.1",
38136
+ "@tailwindcss/postcss": "^4.3.2",
37663
38137
  "@types/react": "^19.2.17",
37664
38138
  "@types/react-dom": "^19.2.3",
37665
- "tailwindcss": "^4.3.1"
38139
+ "tailwindcss": "^4.3.2"
37666
38140
  },
37667
38141
  "scripts": {
37668
38142
  "check-types": "tsc --noEmit"
@@ -39613,8 +40087,8 @@ function SuccessPage() {
39613
40087
  </div>
39614
40088
  `]
39615
40089
  ]);
39616
- const TEMPLATE_COUNT = 506;
40090
+ const TEMPLATE_COUNT = 507;
39617
40091
  //#endregion
39618
- export { EMBEDDED_TEMPLATES, GeneratorError, Handlebars, TEMPLATE_COUNT, VirtualFileSystem, dependencyVersionMap, generate, generateReproducibleCommand, isBinaryFile, processAddonTemplates, processAddonsDeps, processFileContent, processNxConfig, processPackageConfigs, processTemplateString, processTurboConfig, processVitePlusConfig, transformFilename, writeBtsConfigToVfs };
40092
+ export { EMBEDDED_TEMPLATES, GeneratorError, Handlebars, TEMPLATE_COUNT, VirtualFileSystem, dependencyVersionMap, generate, generateReproducibleCommand, isBinaryFile, processAddonTemplates, processAddonsDeps, processFileContent, processNxConfig, processPackageConfigs, processTemplateString, processTurboConfig, processVercelConfig, processVitePlusConfig, transformFilename, writeBtsConfigToVfs };
39619
40093
 
39620
40094
  //# sourceMappingURL=index.mjs.map