create-turbo-stack

Wiring

How create-turbo-stack wires packages together correctly.

The core value of create-turbo-stack is not file generation — it's wiring. Every generated project has correct cross-package references, CSS imports, TypeScript inheritance, environment variables, and dependency versions. This page explains how.

CSS @source Directives

When a Next.js app consumes a UI package that produces CSS, Tailwind needs to scan that package's files. create-turbo-stack generates the correct @source directives automatically.

/* apps/web/src/app/globals.css */
@import "tailwindcss";
@import "@myorg/ui/globals.css";
@source "../../packages/ui/src/**/*.tsx";

All @import rules are emitted before any @source (CSS requires @import to come first), and the @source paths are computed by computeCssSourceMap(), which looks at each app's consumes list and finds packages with producesCSS: true.

Catalog Dependencies

Turborepo's catalog feature lets you pin dependency versions in one place. create-turbo-stack generates a catalog field in the root package.json:

{
  "catalog": {
    "next": "^15.0.0",
    "react": "^19.0.0",
    "typescript": "^5.9.0",
    "@biomejs/biome": "^1.9.0"
  }
}

Individual packages reference these with "catalog:":

{
  "dependencies": {
    "next": "catalog:",
    "react": "catalog:"
  }
}

The catalog is computed by computeCatalog(), which inspects every preset selection and adds the required dependencies with pinned versions.

Workspace References

When an app consumes a package, it needs a workspace dependency. create-turbo-stack computes these automatically:

{
  "dependencies": {
    "@myorg/ui": "workspace:*",
    "@myorg/db": "workspace:*",
    "@myorg/env": "workspace:*"
  }
}

Auto-consumed packages (db, api, auth, env) are added based on preset selections — you don't need to manually specify them in consumes.

TypeScript Config Chain

Every generated project uses a shared typescript-config package. create-turbo-stack generates config files that match each target:

TargetExtends
Next.js app@myorg/typescript-config/nextjs.json
React library / UI@myorg/typescript-config/react.json
Node library@myorg/typescript-config/library.json
Config package@myorg/typescript-config/base.json

Environment Chain

When you select integrations that need environment variables, create-turbo-stack:

  1. Generates a .env.example at root with all required variables
  2. Creates an env package (when envValidation is on) with Zod schemas via @t3-oss/env-nextjs
  3. Adds globalEnv to turbo.json so Turbo knows about them

The env package is load-bearing, not decoration. With validation on, generated provider code imports the typed, validated values instead of reading raw process.env:

// packages/db/src/client.ts
import { env } from "@myorg/env";

const client = postgres(env.DATABASE_URL); // typed, validated — no `!`

The package sets skipValidation: !!process.env.SKIP_ENV_VALIDATION, so builds without secrets (CI, Docker) don't throw — the generated CI workflow sets that flag. When envValidation is off, code falls back to process.env and no env package is created.

Each integration contributes its variables:

IntegrationVariables
SupabaseSUPABASE_URL, SUPABASE_ANON_KEY, NEXT_PUBLIC_SUPABASE_*
Drizzle/PrismaDATABASE_URL
ClerkCLERK_SECRET_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
SentrySENTRY_DSN
PostHogNEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_HOST
ResendRESEND_API_KEY, EMAIL_FROM
UpstashUPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN
AI SDKOPENAI_API_KEY

Turbo Tasks

turbo.json tasks are generated based on selections:

  • Always: build, dev, lint, type-check
  • Drizzle: db:generate, db:migrate, db:push
  • Prisma: db:generate, db:migrate

Build outputs are configured per app type (.next/** for Next.js, dist/** for Hono).

Exports Map

Every package gets a proper exports field in package.json:

{
  "exports": {
    ".": { "types": "./src/index.ts", "default": "./src/index.ts" },
    "./server": { "types": "./src/server.ts", "default": "./src/server.ts" },
    "./client": { "types": "./src/client.ts", "default": "./src/client.ts" }
  }
}

The exports are determined by the package type and what the integration requires (e.g., auth packages always export ./server, ./client, ./middleware).

Linters

The linter choice drives a small registry (getLinter()), so every package's lint script, the root format script, the config files, and the dependencies all come from one place. Each option produces a real, working setup — and the generated project passes its own linter (the e2e harness scaffolds the minimal preset under all three and runs lint).

LinterLintFormatConfig files
biomebiome checkBiomebiome.json
oxlintoxlintPrettier.oxlintrc.json, .prettierrc
eslint-prettiereslint .Prettiereslint.config.mjs (flat, typescript-eslint), .prettierrc

For ESLint, each package re-exports the root flat config so eslint . resolves the same rules everywhere.

On this page