This template is a generic, brand-neutral starting point. Everything is wired
to the @repo/* scope and the display name "Internal Tools" so a fresh clone is
self-describing. This guide takes you from a fresh clone to a renamed,
running project of your own.
If you just want to run the template as-is, see Getting started instead.
On GitHub, click "Use this template" → "Create a new repository" (or fork it). Then clone your new repo:
git clone <your-new-repo> my-app
cd my-app
pnpm installpnpm install links the internal @repo/* packages via workspaces — there's no
build step for them.
There are three things to make yours:
| Thing | From | To (example) | Where |
|---|---|---|---|
| Package scope | @repo |
@acme |
every package.json, every import, configs |
| Workspace name | internal-stack-template |
acme-tools |
root package.json name |
| Display name | "Internal Tools" | "Acme Tools" | NEXT_PUBLIC_APP_NAME in both apps' env files |
The scope appears in package names, internal imports, transpilePackages
arrays, exports maps, and a few config files. Do a workspace-wide
find-and-replace of the literal string @repo with your scope, then reinstall:
# Replace @repo with @acme across the workspace (excludes node_modules / .git).
grep -rl --exclude-dir=node_modules --exclude-dir=.git '@repo' . \
| xargs sed -i 's/@repo/@acme/g'
pnpm installAfter the swap, verify the workspace still resolves:
pnpm typecheckA
scripts/rename-template.tshelper (run viapnpm rename) is the intended way to do this end to end — scope, rootname, andNEXT_PUBLIC_APP_NAMEin one pass, with a confirmation prompt. If your clone doesn't include it yet, the manualgrep | sedabove does the same job; just also update the display name and root package name in the next steps.
The product name is never hardcoded — both apps read it from
NEXT_PUBLIC_APP_NAME (via @repo/env/client's APP_NAME constant, default
"Internal Tools"). Set it in each app's env file:
# apps/web/.env.local and apps/admin/.env.local
NEXT_PUBLIC_APP_NAME=Acme ToolsEdit the root package.json name and description, and the CODEOWNERS /
issue templates under .github/ if you keep them.
Each context loads its own env file. Copy the examples:
cp apps/web/.env.example apps/web/.env.local
cp apps/admin/.env.example apps/admin/.env.local
cp packages/db/.env.example packages/db/.env.localThen fill them in. The must-haves:
-
BETTER_AUTH_SECRET— generate once and paste the same value into both app env files:openssl rand -base64 32
-
DATABASE_URL— required for admin and any DB read. The web app can run without it (it falls back to the seed dataset).
The full variable reference is the root .env.example.
Every variable is validated at boot by @repo/env/* — see
Getting started.
Start local Postgres (Docker), apply the migration, and load the example data:
docker compose up -d db # postgres:16, matches the default DATABASE_URL
pnpm db:migrate # prisma migrate deploy — applies the init migration
pnpm db:seed # idempotent — re-runnablepnpm db:seed upserts the example Item rows (see
Database — seeding). It's safe to re-run.
When you replace the Item model with your own:
- Edit
packages/db/prisma/schema.prisma. - Author a migration:
pnpm db:migrate:dev. - Refresh the client:
pnpm db:generate. - Update the seed and the typed queries (
packages/db/src/queries/,packages/db/src/seed/), and the Zod schemas in@repo/types.
pnpm dev- Public app — http://localhost:3000
- Admin dashboard — http://localhost:3001
Create your first admin by signing up at http://localhost:3001/sign-in — new
sign-ups default to the admin role.
The base template is non-spatial (plain Postgres, no map). Spatial
features — the postgis extension, Unsupported(...) geometry columns, the
ST_* raw-query conventions, a geodetic coordinates package, and a Mapbox
map — live on a separate feat/geo branch so you only carry them when a
project needs them.
To bring them into your project:
git fetch origin feat/geo
# Option A — cherry-pick the geo commits onto your branch:
git log origin/feat/geo --oneline # find the commits you want
git cherry-pick <commit>... # apply them
# Option B — merge the branch wholesale:
git merge origin/feat/geoAfter pulling it in, expect to:
- Swap the
docker-compose.ymlimage frompostgres:16topostgis/postgis:16-3.4(it's commented in the file). - Re-run
pnpm db:migrateso the PostGIS extension and geometry columns are created. - Add the map-related
NEXT_PUBLIC_*env vars the geo branch introduces (its.env.examplefiles document them).
Resolve any conflicts in schema.prisma, the seed, and the env modules — the
geo branch extends the same files the base template ships.
The template is deliberately full-featured. Remove what doesn't apply:
- No file uploads? Drop
@repo/storagefrom the apps' dependencies andtranspilePackages, and delete theR2_*/NEXT_PUBLIC_R2_PUBLIC_URLenv vars from the examples and the@repo/env/storageschema. - No error tracking? Leave
NEXT_PUBLIC_SENTRY_DSNunset — the instrumentation files no-op. Remove@sentry/nextjsonly if you want a smaller install. - Don't need the public app? Keep just
apps/adminand deleteapps/web(and its revalidation wiring).
- Architecture — the big picture
- Database — before you change the schema
- Authentication — before you add protected routes
- Development workflow — day-to-day recipes