Project scaffolded with create-t3-app and selected: Next.js App Router, TypeScript, tRPC, Prisma (PostgreSQL), Tailwind CSS, ESLint/Prettier.
T3 Stack scaffold:
- Next.js App Router (v15)
- TypeScript
- tRPC v11
- Prisma ORM v6 (PostgreSQL)
- Tailwind CSS v4.1
- ESLint + Prettier
- pnpm
Key files and entry points:
- App Router root: src/app/page.tsx
- tRPC HTTP handler: src/app/api/trpc/[trpc]/route.ts
- Server API root: appRouter and type AppRouter
- tRPC plumbing: createTRPCContext(), createTRPCRouter, publicProcedure
- Example router and procedures: postRouter, hello, create, getLatest
- React tRPC client/provider: api export, TRPCReactProvider() + getBaseUrl()
- Prisma schema: prisma/schema.prisma (model Post)
- Environment validation: src/env.js
- Database helper: src/server/db.ts
- Styling entry: src/styles/globals.css
- Local DB helper script: start-database.sh
- Import alias: "@" (see tsconfig.json)
- Node.js 18.18+ (LTS) or 20+
- pnpm 10 (project sets "packageManager": pnpm@10.x in package.json)
- Docker Desktop or Podman (for local PostgreSQL via start-database.sh)
- macOS/Linux/WSL recommended for shell script usage
- Install dependencies:
pnpm install
- Prepare your env file:
cp .env.example .env # then edit .env and set DATABASE_URL
- Start a local PostgreSQL with Docker using the helper script (uses DATABASE_URL from .env):
chmod +x ./start-database.sh # first time only ./start-database.sh
- Apply the already-committed Prisma migrations to your local database (no schema init needed):
pnpm db:migrate # no-op if your DB is already up-to-date
- Run the dev server:
pnpm dev # http://localhost:3000
- Copy example and edit (if you didn’t in step 1):
cp .env.example .env
- Required:
- Update the PostgreSQL password in the .env
- DATABASE_URL: format
postgresql://USER:PASSWORD@HOST:PORT/DB_NAME
- Validation is enforced in src/env.js. If you add more env vars, update this file accordingly.
Option A: Start a local container using the helper script:
# make executable once
chmod +x ./start-database.sh
# start DB using DATABASE_URL from .env
./start-database.sh
Notes about start-database.sh:
- Parses user, password, port, and DB name from your
DATABASE_URL
. - Names the container as "<DB_NAME>-postgres".
- If your password is the default "password", the script can generate a random one and in-place update
.env
. - Requires Docker or Podman daemon running. On Windows, run it from WSL.
Option B: Bring your own PostgreSQL and ensure DATABASE_URL points to it.
- I’ve already initialized the database and committed migrations.
- You do NOT need to run
prisma db push
or create new migrations to get started. - To sync your local database to the committed state, run:
pnpm db:migrate # applies committed migrations
- Explore data (optional):
pnpm db:studio
- For future schema changes (maintainers only): update prisma/schema.prisma and create a migration with:
pnpm db:generate # prisma migrate dev (generates & applies a new migration)
- Development server:
pnpm dev # http://localhost:3000
- Type check:
pnpm typecheck
- Lint & format:
pnpm lint pnpm lint:fix pnpm format:check pnpm format:write
- Production preview:
pnpm preview # builds then starts production server
Server:
- Root router: appRouter
- Context: createTRPCContext() exposes DB via src/server/db.ts
- Public procedure base: publicProcedure
- Example router: postRouter with hello, create, getLatest
- HTTP handler bound in App Router: src/app/api/trpc/[trpc]/route.ts
Client:
- React Query + tRPC client/provider: TRPCReactProvider(), client instance api
- Base URL resolution: getBaseUrl() (uses
window.location
on client orNEXT_PUBLIC_APP_URL
/default for RSC/SSR) - Example server component usage of tRPC RSC helpers: src/app/page.tsx
- Example hydrated component showing the latest post: src/app/_components/post.tsx
- Tailwind v4.1 is preconfigured; global styles live in src/styles/globals.css.
- Add utility classes directly to components in src/app.
- Prettier plugin for Tailwind sorting is enabled via dev dependency.
Useful scripts:
pnpm format:check
pnpm format:write
- Import alias "@" maps to
src/
. See tsconfig.json. - Example:
import { db } from "@/server/db"
resolves to src/server/db.ts.
- Set environment variables (at minimum
DATABASE_URL
) in your hosting provider. - Ensure migrations run in your deployment pipeline:
- Option 1 (managed): run
pnpm db:migrate
as a deploy step. - Option 2 (manual): apply migrations from your CI before starting the app.
- Option 1 (managed): run
postinstall
runsprisma generate
automatically, as defined in package.json.- The tRPC base URL logic getBaseUrl() respects
VERCEL_URL
in serverless environments.
- Port already in use when starting DB:
- Adjust the port in DATABASE_URL and re-run start-database.sh.
- Permission denied for the DB script:
chmod +x ./start-database.sh
- Docker/Podman daemon not running:
- Start Docker Desktop/Podman Desktop and retry.
- Prisma schema changes not reflected:
pnpm db:migrate # apply committed migrations # maintainers: use pnpm db:generate to create a new migration after editing schema.prisma
Defined in package.json:
- build:
next build
- dev:
next dev --turbo
- preview:
next build && next start
- start:
next start
- check:
next lint && tsc --noEmit
- typecheck:
tsc --noEmit
- lint / lint:fix
- format:check / format:write
- db:push / db:generate / db:migrate / db:studio
- App Router pages and UI: src/app
- tRPC server: src/server/api
- tRPC client: src/trpc
- Prisma schema: prisma/schema.prisma
- Global styles: src/styles/globals.css
- Environment schema: src/env.js
- DB helper: src/server/db.ts
- Local DB script: start-database.sh
This repo is configured to allow co-developing seo-foundry alongside pixel-forge without publishing loops. The setup uses:
- pnpm workspaces (see pnpm-workspace.yaml)
- pixel-forge cloned locally at packages/pixel-forge (ignored by the parent repo; see .gitignore)
- workspace dependency resolution ("workspace:*") from this app to pixel-forge in package.json
Quick start (after cloning seo-foundry):
# clone pixel-forge locally into packages/
mkdir -p packages
git clone git@github.com:devints47/pixel-forge.git packages/pixel-forge
# install and link workspaces
pnpm install
# run library watch in one terminal (if pixel-forge exposes "dev")
pnpm --filter pixel-forge dev
# run the app in another terminal
pnpm dev
Notes:
- packages/pixel-forge is intentionally ignored by the parent repo for this local-clone workflow. If you later switch to a Git submodule workflow instead, remove that ignore so the submodule can be tracked by the parent repo.
- When pixel-forge is stable for seo-foundry, switch to a published npm version and remove the local clone; see the full workflow doc.
Full workflow and removal steps are documented at:
A full integration guide with data flow and operational details is provided here:
- Pixel Forge Integration Guide: docs/pixel-forge-integration.md
Quick pointers:
- UI route: open http://localhost:3000/pixel-forge (or your configured NEXT_PUBLIC_API_BASE_URL)
- Server router: pixel-forgeRouter
- newSession, uploadImage, generateAssets, getGenerationProgress, zipAssets, cleanupSession, cleanupExpired
- Session storage utilities: session.ts
- Engine detection (ImageMagick/Jimp): deps.ts
- Client provider: TRPCReactProvider(), api
Recommended toolchain for best results:
- ImageMagick installed on the host to enable the
magick
engine- macOS:
brew install imagemagick
- Debian/Ubuntu:
apt-get install imagemagick
- If unavailable, the system falls back to Jimp, with some quality/feature tradeoffs.
- macOS:
A security-focused overview of hardening and abuse prevention is provided here:
- Security and Abuse-Prevention Design: docs/security.md
Highlights:
- Rate limiting by endpoint (keyed by IP and session), concurrency locks per session
- Path normalization and session-root confinement for all file operations
- Safe urlPrefix enforcement for generated asset URLs
- File-serving route with ETag/Last-Modified, conditional GET (304),
nosniff
, and ZIP attachment headers - Opportunistic TTL cleanup for expired sessions, plus a manual cleanup API
- Client error/info alerts and structured TRPC errors with actionable guidance
Production recommendations:
- Use a shared store (e.g., Redis) for distributed rate limiting and locks
- Move temporary session files to object storage (S3/R2), serve via signed URLs or secure proxy
- Enforce additional limits at the edge (CDN/WAF), tune caching and headers at the proxy layer
- Add observability/metrics for generation runtimes, error rates, and rate-limit hit counts