Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ npx create-better-t-stack@latest
- Databases: SQLite, PostgreSQL, MySQL, MongoDB (or none)
- ORMs: Drizzle, Prisma, Mongoose (or none)
- Auth: Better-Auth (optional)
- Addons: Turborepo, PWA, Tauri, Biome, Husky, Starlight, Fumadocs, Ruler, Ultracite, Oxlint
- Addons: Turborepo, PWA, Tauri, Biome, Husky, Starlight, Fumadocs, Ruler, Ultracite, Oxlint, T3 Env
- Examples: Todo, AI
- DB Setup: Turso, Neon, Supabase, Prisma PostgreSQL, MongoDB Atlas, Cloudflare D1, Docker
- Web Deploy: Cloudflare Workers
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Follow the prompts to configure your project or use the `--yes` flag for default
| **Database Setup** | • Turso (SQLite)<br>• Cloudflare D1 (SQLite)<br>• Neon (PostgreSQL)<br>• Supabase (PostgreSQL)<br>• Prisma Postgres (via Prisma Accelerate)<br>• MongoDB Atlas<br>• None (manual setup) |
| **Authentication** | Better-Auth (email/password, with more options coming soon) |
| **Styling** | Tailwind CSS with shadcn/ui components |
| **Addons** | • PWA support<br>• Tauri (desktop applications)<br>• Starlight (documentation site)<br>• Biome (linting and formatting)<br>• Husky (Git hooks)<br>• Turborepo (optimized builds) |
| **Addons** | • PWA support<br>• Tauri (desktop applications)<br>• Starlight (documentation site)<br>• Biome (linting and formatting)<br>• Husky (Git hooks)<br>• Turborepo (optimized builds)<br>• T3 Env (Type-safe environment variables) |
| **Examples** | • Todo app<br>• AI Chat interface (using Vercel AI SDK) |
| **Developer Experience** | • Automatic Git initialization<br>• Package manager choice (npm, pnpm, bun)<br>• Automatic dependency installation |

Expand All @@ -58,7 +58,7 @@ Options:
--auth Include authentication
--no-auth Exclude authentication
--frontend <types...> Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-nativewind, native-unistyles, none)
--addons <types...> Additional addons (pwa, tauri, starlight, biome, husky, turborepo, fumadocs, ultracite, oxlint, none)
--addons <types...> Additional addons (pwa, tauri, starlight, biome, husky, turborepo, fumadocs, ultracite, oxlint, t3env, none)
--examples <types...> Examples to include (todo, ai, none)
--git Initialize git repository
--no-git Skip git initialization
Expand Down Expand Up @@ -192,7 +192,7 @@ npx create-better-t-stack my-app --frontend none --backend hono --api trpc --dat
- **ORM 'none'**: Can be used when you want to handle database operations manually or use a different ORM.
- **Runtime 'none'**: Only available with Convex backend or when backend is 'none'.
- **Cloudflare Workers runtime**: Only compatible with Hono backend, Drizzle ORM (or no ORM), and SQLite database (with D1 setup). Not compatible with MongoDB.
- **Addons 'none'**: Skips all addons (PWA, Tauri, Starlight, Biome, Husky, Turborepo).
- **Addons 'none'**: Skips all addons (PWA, Tauri, Starlight, Biome, Husky, Turborepo, T3 Env).
- **Examples 'none'**: Skips all example implementations (todo, AI chat).
- **SvelteKit, Nuxt, and SolidJS** frontends are only compatible with oRPC API layer
- **PWA support** requires React with TanStack Router, React Router, or SolidJS
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"shadcn",
"pwa",
"tauri",
"biome"
"biome",
"t3env"
],
"repository": {
"type": "git",
Expand Down
8 changes: 7 additions & 1 deletion apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const DEFAULT_CONFIG_BASE = {
database: "sqlite",
orm: "drizzle",
auth: "better-auth",
addons: ["turborepo"],
addons: ["turborepo", "t3env"],
examples: [],
git: true,
install: true,
Expand Down Expand Up @@ -156,6 +156,11 @@ export const dependencyVersionMap = {
nitropack: "^2.12.4",

dotenv: "^17.2.1",

zod: "^4.0.14",
"@t3-oss/env-core": "^0.13.8",
"@t3-oss/env-nextjs": "^0.13.8",
"@t3-oss/env-nuxt": "^0.13.8",
Comment on lines +160 to +163
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Pin checks: ensure versions exist and are compatible (Zod v4 + @t3-oss/env).*

Zod v4 is specified. Some ecosystems still assume v3; ensure templates and @t3-oss/env* peer ranges are compatible with v4 to avoid runtime/TS errors.


🏁 Script executed:

#!/bin/bash
# Check published versions and peer deps for compatibility.
for pkg in zod @t3-oss/env-core @t3-oss/env-nextjs @t3-oss/env-nuxt; do
  echo "== $pkg latest =="
  npm view "$pkg" version
  echo "== $pkg peerDependencies =="
  npm view "$pkg" peerDependencies --json | jq .
done

Length of output: 1642


Ensure zod version matches @t3-oss/env peer dependency*

The published v0.13.8 of @t3-oss/env-core, env-nextjs, and env-nuxt peer-depend on

"zod": "^3.24.0 || ^4.0.0-beta.0"

which does not include the stable v4.0.14 you’ve pinned. Installing zod@^4.0.14 will trigger a peer-dependency conflict.

You have two remediation paths:

• Pin to a prerelease zod that satisfies the peer range:
– In apps/cli/src/constants.ts (lines 147–150), change

- zod: "^4.0.14",
+ zod: "^4.0.0-beta.0",

This will align with the existing peer-dependency and avoid install-time errors.

• Upgrade to a future @t3-oss/env-* release that adds stable zod v4 support (once published).

🤖 Prompt for AI Agents
In apps/cli/src/constants.ts around lines 147–150, the pinned "zod": "^4.0.14"
conflicts with the peer range declared by @t3-oss/env-* (which accepts "zod":
"^3.24.0 || ^4.0.0-beta.0"); to fix, either change the zod entry to a prerelease
that satisfies the peer range (for example use a ^4.0.0-beta.x prerelease
specifier) so installs don't produce peer-dependency conflicts, or upgrade the
@t3-oss/env-* packages to a future release that formally supports stable zod v4
and then pin zod to a stable v4 once that dependency change is available.

💡 Verification agent

🧩 Analysis chain

Consider gating/removing dotenv when T3 Env is present.

Since T3 Env is now default and the guidelines discourage dotenv with Bun, ensure scaffolding doesn’t include both, to avoid duplicated/conflicting env loading.


🏁 Script executed:

#!/bin/bash
# Find any templates or code that still import/use dotenv.
rg -nP -C2 '\bdotenv\b|\bconfig\s*\(' apps/cli/templates apps || true

Length of output: 14762


Remove direct dotenv usage from CLI scaffolding now that T3 Env is the default

T3 Env packages (@t3-oss/env-core, env-nextjs, env-nuxt, etc.) now handle .env loading across runtimes, and Bun auto-loads .env files out of the box. Keeping dotenv imports/dependencies leads to duplicated or conflicting environment loading. Please remove or gate all direct dotenv usage in your scaffolding and helper code:

• In apps/cli/src/constants.ts
– Remove the "dotenv": "^17.2.1", entry.

• In all Prisma templates (apps/cli/templates/db/prisma/*/prisma.config.ts.hbs)
– Drop the top-level import "dotenv/config";.

• In backend server templates (apps/cli/templates/backend/server/**/src/index.ts.hbs)
– Remove any import "dotenv/config"; (Hono, Elysia, Fastify, Express). If you still need to support plain Node projects without Bun, you can instead wrap this import under a {{#if (eq runtime "node")}}…{{/if}} check.

• In package-template JSON files
apps/cli/templates/backend/server/server-base/package.json.hbs and …/next/package.json.hbs: drop the "dotenv": "^17.2.1" dependency.

• In deployment scripts and helpers
apps/cli/templates/deploy/alchemy/alchemy.run.ts.hbs: remove the import { config } from "dotenv"; and all config({ path: … }) calls.
– All Alchemy setup helpers (alchemy-*.ts under src/helpers/deployment/alchemy/): remove "dotenv" from the devDependencies arrays passed to addPackageDependency.

• In database-provider setup code
apps/cli/src/helpers/database-providers/prisma-postgres-setup.ts: stop injecting import "dotenv/config"; into the generated Prisma config.

By eliminating these, you’ll avoid double-loading environment variables and fully rely on T3 Env’s loading across both Bun and Node runtimes.

🤖 Prompt for AI Agents
In apps/cli/src/constants.ts around lines 147 to 150, the package list still
includes a direct dotenv dependency which must be removed; delete the line
containing "dotenv": "^17.2.1" and ensure no other code in this file imports or
references dotenv. Also sweep the CLI scaffolding and templates as described in
the review: remove top-level import "dotenv/config" from Prisma and backend
server template files (or guard it with a {{#if (eq runtime "node")}}…{{/if}}),
drop "dotenv" from the package-template JSON files listed, remove dotenv imports
and config(...) calls from deployment scripts/helpers, and stop injecting import
"dotenv/config"; in prisma-postgres-setup.ts so T3 Env/Bun handle .env loading
exclusively.

} as const;

export type AvailableDependencies = keyof typeof dependencyVersionMap;
Expand All @@ -171,5 +176,6 @@ export const ADDON_COMPATIBILITY: Record<Addons, readonly Frontend[]> = {
ruler: [],
oxlint: [],
fumadocs: [],
t3env: [],
none: [],
} as const;
6 changes: 6 additions & 0 deletions apps/cli/src/helpers/addons/addons-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getPackageExecutionCommand } from "../../utils/package-runner";
import { setupFumadocs } from "./fumadocs-setup";
import { setupVibeRules } from "./ruler-setup";
import { setupStarlight } from "./starlight-setup";
import { setupT3Env } from "./t3env-setup";
import { setupTauri } from "./tauri-setup";
import { setupUltracite } from "./ultracite-setup";
import { addPwaToViteConfig } from "./vite-pwa-setup";
Expand Down Expand Up @@ -58,6 +59,11 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
) {
await setupTauri(config);
}

if (addons.includes("t3env")) {
await setupT3Env(config);
}
Comment on lines +63 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Ensure zod is installed for Next.js web when adding T3 Env

The client/server env templates rely on zod. In t3env-setup.ts, Next.js branch currently installs @t3-oss/env-nextjs only. It should also add zod to the web app to avoid build-time errors in env/client.ts and/or env/server.ts.

Apply this diff in apps/cli/src/helpers/addons/t3env-setup.ts:

-      if (hasNext) {
-        await addPackageDependency({
-          dependencies: ["@t3-oss/env-nextjs"],
-          projectDir: webDir,
-        });
-      } else if (hasNuxt) {
+      if (hasNext) {
+        await addPackageDependency({
+          dependencies: ["@t3-oss/env-nextjs", "zod"],
+          projectDir: webDir,
+        });
+      } else if (hasNuxt) {
         // For Nuxt, we'll use the core package
         await addPackageDependency({
           dependencies: ["@t3-oss/env-nuxt", "zod"],
           projectDir: webDir,
         });
       } else {
         // For other frameworks, use the core package
         await addPackageDependency({
           dependencies: ["@t3-oss/env-core", "zod"],
           projectDir: webDir,
         });
       }

Also confirm dependencyVersionMap includes pinned versions for zod, @t3-oss/env-core, @t3-oss/env-nextjs, and @t3-oss/env-nuxt to keep installs deterministic.


🏁 Script executed:

#!/bin/bash
# Inspect Next.js branch in t3env-setup.ts to verify if "zod" is included
rg -n "addPackageDependency" -C3 --glob "apps/cli/src/helpers/addons/t3env-setup.ts"

# Locate dependencyVersionMap to ensure pinned versions for required packages
rg -n "dependencyVersionMap" -C3

Length of output: 5018


🏁 Script executed:

#!/bin/bash
# Display the section of dependencyVersionMap around @t3-oss/env-core, @t3-oss/env-nextjs, and zod
sed -n '120,160p' apps/cli/src/constants.ts

Length of output: 1224


Ensure zod is added to the Next.js branch in t3env-setup.ts

The Next.js path in setupT3Env currently installs only @t3-oss/env-nextjs, but the generated env/client.ts/env/server.ts rely on zod, which will lead to build failures.

• File: apps/cli/src/helpers/addons/t3env-setup.ts (around line 38)

-      if (hasNext) {
-        await addPackageDependency({
-          dependencies: ["@t3-oss/env-nextjs"],
-          projectDir: webDir,
-        });
-      } else if (hasNuxt) {
+      if (hasNext) {
+        await addPackageDependency({
+          dependencies: ["@t3-oss/env-nextjs", "zod"],
+          projectDir: webDir,
+        });
+      } else if (hasNuxt) {

• I’ve confirmed in apps/cli/src/constants.ts that dependencyVersionMap pins versions for
zod (^4.0.14)
@t3-oss/env-core (^0.13.8)
@t3-oss/env-nextjs (^0.13.8)
@t3-oss/env-nuxt (^0.13.8)
so installs remain deterministic.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (addons.includes("t3env")) {
await setupT3Env(config);
}
if (hasNext) {
await addPackageDependency({
dependencies: ["@t3-oss/env-nextjs", "zod"],
projectDir: webDir,
});
} else if (hasNuxt) {
🤖 Prompt for AI Agents
In apps/cli/src/helpers/addons/t3env-setup.ts around line 38, the Next.js branch
of setupT3Env currently only installs @t3-oss/env-nextjs but omits zod (and
should also include @t3-oss/env-core), causing generated env/client.ts and
env/server.ts to fail; update the Next.js install step to add zod and
@t3-oss/env-core to the list of packages to install (using the pinned versions
from dependencyVersionMap/constants) so installs remain deterministic and match
the generated code's runtime dependencies.


const hasUltracite = addons.includes("ultracite");
const hasBiome = addons.includes("biome");
const hasHusky = addons.includes("husky");
Expand Down
64 changes: 64 additions & 0 deletions apps/cli/src/helpers/addons/t3env-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import path from "node:path";
import { spinner } from "@clack/prompts";
import { consola } from "consola";
import fs from "fs-extra";
import pc from "picocolors";
import type { ProjectConfig } from "../../types";
import { addPackageDependency } from "../../utils/add-package-deps";

export async function setupT3Env(config: ProjectConfig) {
const { frontend, backend, projectDir } = config;
const s = spinner();

// T3 Env requires a backend to be present
if (backend === "none") {
s.stop(pc.yellow("T3 Env requires a backend to be configured"));
return;
}
Comment on lines +13 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect early return and spinner misuse block web-only setups.

  • T3 Env does not require a separate backend; Next/Nuxt-only projects should still be configured.
  • Calling s.stop() before s.start() is an API misuse.

Refactor to always process the web app and only skip the server section when no backend/server app exists.

-	// T3 Env requires a backend to be present
-	if (backend === "none") {
-		s.stop(pc.yellow("T3 Env requires a backend to be configured"));
-		return;
-	}
+	// Proceed regardless of backend; we'll conditionally configure server/web below.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// T3 Env requires a backend to be present
if (backend === "none") {
s.stop(pc.yellow("T3 Env requires a backend to be configured"));
return;
}
- // T3 Env requires a backend to be present
- if (backend === "none") {
- s.stop(pc.yellow("T3 Env requires a backend to be configured"));
- return;
// Proceed regardless of backend; we'll conditionally configure server/web below.
🤖 Prompt for AI Agents
In apps/cli/src/helpers/addons/t3env-setup.ts around lines 13 to 17, remove the
early return and the s.stop() call so web-only projects still run; always run
the web app configuration path and only skip the server/backend configuration
when backend === "none" (or when there is no server app detected). Ensure you
never call s.stop() before s.start(): start the spinner before any long-running
step and stop it after that specific step completes or fails, and isolate
spinner usage to the server/backend section so skipping the server does not
attempt to stop a spinner that was never started.


try {
s.start("Setting up T3 Env for type-safe environment variables...");

// Add dependencies to server
const serverDir = path.join(projectDir, "apps/server");
if (await fs.pathExists(serverDir)) {
await addPackageDependency({
dependencies: ["@t3-oss/env-core", "zod"],
projectDir: serverDir,
});
}
Comment on lines +19 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Start the spinner before any conditional exits; improve messaging.

Begin the spinner first, then handle per-app setup. This avoids calling stop before start and provides clearer UX.

-	try {
-		s.start("Setting up T3 Env for type-safe environment variables...");
+	try {
+		s.start("Setting up T3 Env for type-safe environment variables...");

Optionally, after computing presence, add a note if neither app exists:

+		const serverDir = path.join(projectDir, "apps/server");
+		const webDir = path.join(projectDir, "apps/web");
+		const hasServer = await fs.pathExists(serverDir);
+		const hasWeb = await fs.pathExists(webDir);
+		if (!hasServer && !hasWeb) {
+			s.stop(pc.yellow("No apps/server or apps/web found; skipping T3 Env setup."));
+			return;
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
s.start("Setting up T3 Env for type-safe environment variables...");
// Add dependencies to server
const serverDir = path.join(projectDir, "apps/server");
if (await fs.pathExists(serverDir)) {
await addPackageDependency({
dependencies: ["@t3-oss/env-core", "zod"],
projectDir: serverDir,
});
}
try {
s.start("Setting up T3 Env for type-safe environment variables...");
const serverDir = path.join(projectDir, "apps/server");
const webDir = path.join(projectDir, "apps/web");
const hasServer = await fs.pathExists(serverDir);
const hasWeb = await fs.pathExists(webDir);
if (!hasServer && !hasWeb) {
s.stop(pc.yellow("No apps/server or apps/web found; skipping T3 Env setup."));
return;
}
// Add dependencies to server
if (hasServer) {
await addPackageDependency({
dependencies: ["@t3-oss/env-core", "zod"],
projectDir: serverDir,
});
}
🤖 Prompt for AI Agents
In apps/cli/src/helpers/addons/t3env-setup.ts around lines 19 to 29, the spinner
is started only inside a branch which can lead to stop being called before start
and unclear messaging; move s.start("Setting up T3 Env for type-safe environment
variables...") to before checking app directories so the spinner begins
unconditionally, then compute existence of apps (server/client) and run per-app
setup while updating spinner messages per-app; if neither app exists, update the
spinner or log a clear message indicating no apps found and then stop the
spinner.


// Add framework-specific dependencies to web app
const webDir = path.join(projectDir, "apps/web");
if (await fs.pathExists(webDir)) {
const hasNext = frontend.includes("next");
const hasNuxt = frontend.includes("nuxt");

if (hasNext) {
await addPackageDependency({
dependencies: ["@t3-oss/env-nextjs"],
projectDir: webDir,
});
} else if (hasNuxt) {
Comment on lines +37 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Next.js branch should also add zod.

Templates typically import zod schemas; env-nextjs doesn’t pull zod transitively. Add it to avoid compile-time errors.

-			if (hasNext) {
+			if (hasNext) {
 				await addPackageDependency({
-					dependencies: ["@t3-oss/env-nextjs"],
+					dependencies: ["@t3-oss/env-nextjs", "zod"],
 					projectDir: webDir,
 				});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (hasNext) {
await addPackageDependency({
dependencies: ["@t3-oss/env-nextjs"],
projectDir: webDir,
});
} else if (hasNuxt) {
if (hasNext) {
await addPackageDependency({
dependencies: ["@t3-oss/env-nextjs", "zod"],
projectDir: webDir,
});
} else if (hasNuxt) {
🤖 Prompt for AI Agents
In apps/cli/src/helpers/addons/t3env-setup.ts around lines 37 to 42, the Next.js
branch adds @t3-oss/env-nextjs but omits zod which is not pulled transitively;
update the call to addPackageDependency in the hasNext branch to also include
"zod" in the dependencies array so zod is installed alongside @t3-oss/env-nextjs
to prevent compile-time errors from missing schema imports.

// For Nuxt, we'll use the core package
await addPackageDependency({
dependencies: ["@t3-oss/env-nuxt", "zod"],
projectDir: webDir,
});
} else {
// For other frameworks, use the core package
await addPackageDependency({
dependencies: ["@t3-oss/env-core", "zod"],
projectDir: webDir,
});
}
}

s.stop("T3 Env configured successfully!");
} catch (error) {
s.stop(pc.red("Failed to set up T3 Env"));
if (error instanceof Error) {
consola.error(pc.red(error.message));
}
}
}
2 changes: 2 additions & 0 deletions apps/cli/src/helpers/core/create-readme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ function generateFeaturesList(
addonsList.push("- **Starlight** - Documentation site with Astro");
} else if (addon === "turborepo") {
addonsList.push("- **Turborepo** - Optimized monorepo build system");
} else if (addon === "t3env") {
addonsList.push("- **T3 Env** - Type-safe environment variables");
}
}

Expand Down
6 changes: 5 additions & 1 deletion apps/cli/src/prompts/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } {
label = "Fumadocs";
hint = "Build excellent documentation site";
break;
case "t3env":
label = "T3 Env";
hint = "Type-safe environment variables";
break;
default:
label = addon;
hint = `Add ${addon}`;
Expand All @@ -69,7 +73,7 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } {
const ADDON_GROUPS = {
Documentation: ["starlight", "fumadocs"],
Linting: ["biome", "oxlint", "ultracite"],
Other: ["ruler", "turborepo", "pwa", "tauri", "husky"],
Other: ["ruler", "turborepo", "pwa", "tauri", "husky", "t3env"],
};

export async function getAddonsChoice(
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const AddonsSchema = z
"fumadocs",
"ultracite",
"oxlint",
"t3env",
"none",
])
.describe("Additional addons");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "@/env/server";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Same alias-resolution pitfall: switch to a relative import in server Next config

Using "@/env/server" in next.config.ts is prone to resolution failures. Use a relative path.

-import "@/env/server";
+// If next.config.ts is at the app root:
+import "./src/env/server";
+// If placed under src/:
+// import "./env/server";

Double-check the final file location produced by the generator and align the relative path accordingly.


🏁 Script executed:

#!/bin/bash
set -e

# Print current working directory
pwd

# List files under the Next.js config directory
find apps/cli/templates/addons/t3env/apps/server/next -maxdepth 2 -type f -print

# Find environment schema files in the template
find apps/cli/templates/addons/t3env -type f \( -name "server.ts" -o -name "env.ts" \) -print

Length of output: 274


🏁 Script executed:

#!/bin/bash
set -e

echo "Listing all files under apps/cli/templates/addons/t3env:"
find apps/cli/templates/addons/t3env -type f

Length of output: 518


🏁 Script executed:

#!/bin/bash
# Display the beginning of the Next.js server template config to inspect import paths
sed -n '1,100p' apps/cli/templates/addons/t3env/apps/server/next/next.config.ts

Length of output: 102


Switch to a relative import in next.config.ts

Importing via the @/... alias in your Next.js config isn’t supported at load time, causing resolution failures. Update the import to a relative path from the next.config.ts file to the generated server.ts module:

• File needing update:
apps/server/next/next.config.ts

• Change the import to point at src/env/server.ts:

-import "@/env/server";
+import "./src/env/server";

Verify this path matches your final project structure (e.g., if next.config.ts ends up under a different directory, adjust accordingly).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import "@/env/server";
import "./src/env/server";
🤖 Prompt for AI Agents
In apps/cli/templates/addons/t3env/apps/server/next/next.config.ts around line
1, the file uses an absolute alias import "@" which Next.js can't resolve at
config load time; replace the aliased import with a relative import pointing to
the generated server module (e.g., "./src/env/server" or "../src/env/server"
depending on next.config.ts location) so the config uses a filesystem path;
update the import to the correct relative path to src/env/server and verify the
path matches the final project layout.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
BETTER_AUTH_SECRET: z.string().min(1),
BETTER_AUTH_URL: z.string().url(),
CORS_ORIGIN: z.string().url(),
{{#if (includes examples 'ai')}}
GOOGLE_GENERATIVE_AI_API_KEY: z.string().min(1),
{{/if}}
},
experimental__runtimeEnv: process.env,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
client: {
NEXT_PUBLIC_SERVER_URL: z.string().url(),
{{#if (eq backend 'convex')}}
NEXT_PUBLIC_CONVEX_URL: z.string().url(),
{{/if}}
},
Comment on lines +5 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Keep client keys and runtimeEnv in lockstep.

Given the conditional Convex block, any future additions to client must be mirrored in runtimeEnv. T3 Env will usually catch mismatches at compile time, but template drift can still happen during refactors.

Run this quick repo check to ensure the Next template importing this file resolves path aliases and that the dependency is declared:

Also applies to: 11-16


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Verify next.config imports the client env (fail fast validation at build time)
rg -n --color=always $'from\\s+[\'"]@/env/client[\'"]' apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs || {
  echo "Missing import of '@/env/client' in next.config.ts.hbs" >&2; exit 1; }

# 2) Ensure '@' path alias exists in the Next web template tsconfig
tsconfigs=$(fd -t f --full-path 'apps/cli/templates/addons/t3env/apps/web/next/.*tsconfig.*' || true)
if [ -n "$tsconfigs" ]; then
  echo "$tsconfigs" | xargs -I{} rg -n -C2 --color=always '"paths"\s*:\s*\{\s*"@/\*"\s*:\s*\["\./src/\*"\]' {} || {
    echo "Missing '@/ -> ./src/*' path alias in Next web tsconfig" >&2; exit 1; }
else
  echo "No tsconfig found for Next web template; please verify alias setup." >&2; exit 1
fi

# 3) Confirm the dependency is declared somewhere in templates
rg -n --color=always '@t3-oss/env-nextjs' || {
  echo "Dependency '@t3-oss/env-nextjs' not found in template manifests" >&2; exit 1; }

Length of output: 281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 2) Verify tsconfig path alias for '@/*'
echo "Checking tsconfig path aliases..."
tsconfigs=$(fd -t f --full-path 'apps/cli/templates/addons/t3env/apps/web/next/.*tsconfig.*' || true)
if [ -n "$tsconfigs" ]; then
  echo "$tsconfigs" | while read -r f; do
    echo "-- Inspecting $f --"
    rg -n -C2 '"paths"\s*:\s*\{[^}]*"@/\*"\s*:\s*\["\.\/src\/\*"\]' "$f" \
      || echo "Missing '@/*' path alias in $f"
  done
else
  echo "No tsconfig found for Next web template"
fi

# 3) Confirm the dependency '@t3-oss/env-nextjs'
echo
echo "Checking for '@t3-oss/env-nextjs' dependency..."
rg -n '@t3-oss/env-nextjs' || echo "Dependency '@t3-oss/env-nextjs' not found in template manifests"

Length of output: 836


Action required: Add missing client env import & verify path alias

The repo check indicates two critical gaps in the Next web template:

  • Missing import of the client env in next.config.ts.hbs.
    The file apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs does not import the client-side environment, causing failsafe validation to be bypassed run1.
  • No TypeScript config found for path alias.
    Our search didn’t locate any tsconfig.json under apps/cli/templates/addons/t3env/apps/web/next/, so the @/* → ./src/* alias may be unset or in a differently named config run2.

The dependency @t3-oss/env-nextjs is already declared and used in templates—no action needed there run2.

Please address the following:

• In apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs, add at the top:

+ import { createEnv } from '@/env/client';

• Locate (or create) the Next web template’s TypeScript config (tsconfig.json or tsconfig.base.json) and ensure it includes:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
🤖 Prompt for AI Agents
In apps/cli/templates/addons/t3env/apps/web/next/src/client.ts.hbs around lines
5 to 10, the template defines client env schema but the Next template
next.config.ts.hbs is missing the required import of the generated client env;
update apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs to
import the client env at the top (import { clientEnv } from
'<path-to-client-env>') and use it with env-nextjs middleware as needed, and
also locate or create the Next web template TypeScript config (tsconfig.json or
tsconfig.base.json) under apps/cli/templates/addons/t3env/apps/web/next/ and add
the compilerOptions.paths mapping to map "@/*" to "./src/*" so the import path
alias resolves correctly.

runtimeEnv: {
NEXT_PUBLIC_SERVER_URL: process.env.NEXT_PUBLIC_SERVER_URL,
{{#if (eq backend 'convex')}}
NEXT_PUBLIC_CONVEX_URL: process.env.NEXT_PUBLIC_CONVEX_URL,
{{/if}}
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "@/env/client";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Avoid path alias in next.config; use a relative import

@/ may not resolve when Next evaluates next.config.ts (Node context). Prefer a relative import to ensure env validation runs at build-time.

Apply:

-import "@/env/client";
+import "./env/client";

If this file is generated at the project root instead of src/, use:

import "./src/env/client";

Also consider switching to the server env import if present (often recommended): ./env/server.


To verify the recommendation:


🏁 Script executed:

#!/bin/bash
# 1. Show the import in the template
sed -n '1,50p' apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs

# 2. Confirm any tsconfig path aliases
rg -n '"paths"' -g 'tsconfig.json' -C3
rg -n '"@/\*"' -g 'tsconfig.json' -C3

Length of output: 876


🌐 Web query:

Next.js tsconfig path aliases next.config.js Node context

💡 Result:

Short summary and practical guidance.

  1. What Next.js supports
  • Next.js natively reads tsconfig.json/jsconfig.json "baseUrl" and "paths" so your imports inside the Next.js app/pages/app code (client + server-side rendering) work with aliases (e.g. import Foo from "@/components/Foo"). [1]
  1. What does NOT work automatically
  • Those TypeScript path mappings are a compile-time / bundler convenience for Next.js and your editor — Node (runtime) does not automatically understand TS "paths". That means standalone Node scripts, ts-node processes, or code executed outside Next’s build pipeline (including some custom code you require from next.config.* or separate server scripts) will NOT resolve tsconfig "paths" unless you add runtime resolution. [1][2]
  1. Common solutions (pick one)
  • For Next.js code: just use tsconfig "baseUrl"/"paths" — no extra config needed. Example tsconfig snippet:
    {
    "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/components/": ["components/"] }
    }
    } [1]

  • For next.config.ts behavior:

    • You can use next.config.ts (typed), but note module resolution in next.config.ts is limited (CommonJS interoperability caveats). You can set typescript.tsconfigPath in next.config to point an alternate tsconfig for builds. [2]
  • For Node runtime (scripts, ts-node, custom server, CLI tools, or if you need aliases inside code executed outside Next’s bundler):

    • Use tsconfig-paths with ts-node: install tsconfig-paths and require its register (or configure ts-node to require it). Example: npm i -D tsconfig-paths and run node -r tsconfig-paths/register or add "ts-node": { "require": ["tsconfig-paths/register"] } to tsconfig. This makes TS "paths" resolved at runtime. [3]
    • Or use module-alias (npm package) and register aliases at startup (or via package.json _moduleAliases).
    • Or use a bundler/babel plugin (babel-plugin-module-resolver) for packages that need compiled aliasing.
    • Or simply use relative or absolute Node-style imports (path.resolve) in code executed by Node.
  1. Quick recommendations
  • Keep tsconfig "paths" for app imports (simplest).
  • For scripts run with ts-node, add tsconfig-paths/register.
  • For next.config.* prefer relative requires or avoid relying on TS path aliases in that file; if you use next.config.ts test behavior locally and in CI. [1][2][3]

Sources

  • Next.js docs: Absolute imports & module path aliases. [1]
  • Next.js TypeScript config & next.config.ts notes (typescript.tsconfigPath, CommonJS note). [2]
  • Runtime resolution with ts-node + tsconfig-paths (practical solution). [3]

Use a relative import in next.config.ts.hbs
Next evaluates next.config.ts in Node (outside Next’s bundler), so TS path aliases like @/… won’t resolve at runtime. Replace with a relative import:

-import "@/env/client";
+import "./env/client";

If your config is at the project root instead of src/, use:

import "./src/env/client";
🤖 Prompt for AI Agents
In apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs around
line 1, the file uses a TS path alias import ("@/env/client") which fails at
Node runtime because next.config.ts is evaluated outside Next's bundler; change
the import to a relative path pointing to the same file (e.g.,
"./src/env/client" if next.config.ts.hbs lives at project root referencing
src/env/client, or "./env/client" if the config file is inside src/) so Node can
resolve it without TypeScript path aliases.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createEnv } from "@t3-oss/env-nuxt";
import { z } from "zod";

export const env = createEnv({
client: {
NUXT_PUBLIC_SERVER_URL: z.string().url(),
{{#if (eq backend 'convex')}}
NUXT_PUBLIC_CONVEX_URL: z.string().url(),
{{/if}}
}
});
2 changes: 2 additions & 0 deletions apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "./env";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Import path and unconditional import will break non-t3env scaffolds

  • Path is wrong: the generated file is src/env/client.ts, not ./env.
  • Import is unconditional; projects without the addon will fail at build/TS time.

Gate the import behind the addon flag and point to the correct file.

Apply:

- import "./env";
+ {{#if (includes addons "t3env")}}import "~/env/client";{{/if}}

If the includes helper isn’t available in your Handlebars runtime, switch to whatever helper you already use for array membership (or add one) and keep the same intent.

To confirm helper availability across templates:


🏁 Script executed:

#!/usr/bin/env bash
# Look for existing array-membership helpers in templates
rg -n -C2 "{{#if \(includes " apps/cli/templates || true
rg -n -C2 "{{#if \(some " apps/cli/templates || true

Length of output: 293


🏁 Script executed:

#!/usr/bin/env bash
# Search for includes helper usage in Handlebars templates
rg -nF '{{#if (includes' -g '*.hbs' apps/cli/templates || true

# Search for 'some' helper usage in Handlebars templates
rg -nF '{{#if (some' -g '*.hbs' apps/cli/templates || true

# Search for includes helper registration in TypeScript code
rg -nP 'registerHelper\(\s*["'\'']?includes["'\'']?,' -g '*.ts' apps/cli || true

# Search for any helper registrations to locate available helpers
rg -n 'registerHelper' -g '*.ts' apps/cli || true

Length of output: 4376


🏁 Script executed:

#!/usr/bin/env bash
# Display the beginning of the helper registration file to verify available helpers
sed -n '1,200p' apps/cli/src/utils/template-processor.ts

Length of output: 1334


Fix conditional Nuxt env import for t3env addon

The Nuxt scaffold currently unconditionally imports ./env, but the generated file actually lives at src/env/client.ts, and projects without the t3env addon will break at build time. We’ve confirmed that the includes helper is already registered (see apps/cli/src/utils/template-processor.ts) and used throughout other templates.

• File to update:
• apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs (line 1)
• Change the import to be conditional on the t3env addon and point to the correct path:

- import "./env";
+ {{#if (includes addons "t3env")}}import "~/env/client";{{/if}}

This ensures that only projects with the t3env addon include the client‐side env shim, and the module resolution aligns with the actual file location.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import "./env";
++ b/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs
@@ -1,1 +1,1
{{#if (includes addons "t3env")}}import "~/env/client";{{/if}}
🤖 Prompt for AI Agents
In apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs around line 1, the
template currently unconditionally imports ./env which breaks projects without
the t3env addon and points to the wrong location; update the template to
conditionally include the client shim only when the t3env addon is present by
using the existing includes helper to wrap the import, and change the import
target to the correct client path (src/env/client.ts) so only projects with
t3env will import the client-side env shim.


// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: 'latest',
Expand Down
1 change: 1 addition & 0 deletions apps/web/content/docs/cli/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ Additional features to include:
- `ultracite`: Ultracite configuration
- `oxlint`: Oxlint fast linting
- `ruler`: Centralize your AI rules with Ruler
- `t3env`: Type-safe environment variables

```bash
create-better-t-stack --addons pwa biome husky
Expand Down
2 changes: 1 addition & 1 deletion apps/web/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Skip prompts and use the default stack:
--database sqlite \
--orm drizzle \
--auth better-auth \
--addons turborepo
--addons turborepo t3env
```
</Tab>
</Tabs>
Expand Down
20 changes: 20 additions & 0 deletions apps/web/public/icon/t3env.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/web/src/app/(home)/_components/npm-package.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ const NpmPackage = () => {
);
};

export default NpmPackage;
export default NpmPackage;
8 changes: 6 additions & 2 deletions apps/web/src/app/(home)/analytics/_components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,13 +369,17 @@ export const addonsConfig = {
label: "Starlight",
color: "hsl(var(--chart-5))",
},
t3env: {
label: "T3 Env",
color: "hsl(var(--chart-6))",
},
turborepo: {
label: "Turborepo",
color: "hsl(var(--chart-6))",
color: "hsl(var(--chart-7))",
},
none: {
label: "No Addons",
color: "hsl(var(--chart-7))",
color: "hsl(var(--chart-8))",
},
Comment on lines +378 to 383
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify availability of CSS token --chart-8 before using it.

You bumped turborepo to --chart-7 and none to --chart-8. Ensure --chart-8 exists in the theme; otherwise charts will fallback or render inconsistently.


🏁 Script executed:

#!/bin/bash
# Verify that --chart-8 is defined somewhere in CSS/vars.
rg -nP --type css --type scss --type tsx --type ts -C2 'var\(--chart-8\)' || true
rg -nP --type css --type scss --type tsx --type ts -C2 '--chart-8:' || true

Length of output: 599


🏁 Script executed:

#!/bin/bash
# Search for any definitions or usages of --chart-7 and --chart-8 across the codebase

# Find where --chart-7 is defined to locate the CSS variable declarations
rg -n -- '--chart-7' -C2

# Find any occurrences of --chart-8 (definition or usage)
rg -n -- '--chart-8' -C2

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Correctly search for CSS variable definitions and usages of --chart-7 and --chart-8

# 1. Definitions in CSS/SCSS files
rg -n -C2 '\-\-chart-(7|8)\s*:' -g '*.css' -g '*.scss'

# 2. Usages via var(...) in CSS/SCSS/TS/TSX
rg -n -C2 'var\(--chart-(7|8)\)' -g '*.css' -g '*.scss' -g '*.ts' -g '*.tsx'

Length of output: 10659


Add missing CSS variable --chart-8 in your global styles

The lookup shows that --chart-8 is referenced in apps/web/src/app/(home)/analytics/_components/types.ts (lines 365–366) but not defined in your global stylesheet (only --chart-5, --chart-6, and --chart-7 exist). Without a definition, any usage of hsl(var(--chart-8)) will be invalid and render inconsistently.

Please add a matching entry for --chart-8 in apps/web/src/app/global.css alongside the other chart variables:

• File: apps/web/src/app/global.css
• Insert after the existing --chart-7 definition

diff --git a/apps/web/src/app/global.css b/apps/web/src/app/global.css
index abcdef0..1234567 100644
--- a/apps/web/src/app/global.css
+++ b/apps/web/src/app/global.css
@@ -191,6 +191,7 @@
 	--chart-5: 27 87% 67%;
 	--chart-6: 210 80% 55%;
 	--chart-7: 330 75% 60%;
+	--chart-8:  90  70%  50%;  /* <-- define this to match your theme palette */
 	--sidebar: oklch(0.93 0.01 264.52);
 	--sidebar-foreground: oklch(0.44 0.04 279.33);

Adjust the HSL values (90 70% 50%) to fit your design’s color scheme. Once defined, hsl(var(--chart-8)) will resolve correctly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
color: "hsl(var(--chart-7))",
},
none: {
label: "No Addons",
color: "hsl(var(--chart-7))",
color: "hsl(var(--chart-8))",
},
++ b/apps/web/src/app/global.css
@@ -191,6 +191,7 @@
--chart-5: 27 87% 67%;
--chart-6: 210 80% 55%;
--chart-8: 90 70% 50%; /* define this to match your theme palette */
--sidebar: oklch(0.93 0.01 264.52);
--sidebar-foreground: oklch(0.44 0.04 279.33);
🤖 Prompt for AI Agents
In apps/web/src/app/(home)/analytics/_components/types.ts around lines 361–366
the color uses hsl(var(--chart-8)) but --chart-8 is missing from the global
stylesheet; open apps/web/src/app/global.css and add a --chart-8 variable entry
immediately after the existing --chart-7 definition (use HSL values appropriate
for your theme, e.g., "90 70% 50%" or other chosen values) so that
hsl(var(--chart-8)) resolves correctly across the app.

} satisfies ChartConfig;

Expand Down
1 change: 1 addition & 0 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const metadata: Metadata = {
"hono",
"elysia",
"turborepo",
"t3env",
"trpc",
"orpc",
"turso",
Expand Down
Loading