Tech stack
Languages, frameworks, runtime choices.
03 — Tech Stack
Locked recommendations for v0.1 / v0.2. Each choice has a documented reason and a fallback — we don't change them on a whim.
Runtime & language
| Layer | Choice | Rationale |
|---|---|---|
| Language | TypeScript 5.x (strict) | Shared types end-to-end |
| Runtime | Node 22 LTS | Native fetch, native test runner, ES modules |
| Package manager | pnpm 9 | Faster than npm, content-addressable store, strict deps |
| Monorepo | Turborepo | Caching, pipeline graph, zero config |
| Lint + format | Biome | One tool, faster than ESLint + Prettier |
Frontend
| Layer | Choice | Rationale |
|---|---|---|
| Framework | Next.js 15 (App Router) | Server actions + RSC + edge-friendly |
| React | 19 | Concurrent + Suspense, async transitions |
| Styling | Tailwind v4 | Native CSS variables, design-token first |
| Components | shadcn/ui | Owned source, no runtime cost |
| Editor | Tiptap v2 | Best-in-class extensibility on ProseMirror |
| Forms | React Hook Form + Zod | Same Zod schemas as the API |
| State | RSC + URL state, then Zustand for client islands | Avoid global stores |
Backend
| Layer | Choice | Rationale |
|---|---|---|
| HTTP | Next.js Route Handlers for public API | Same runtime as dashboard |
| Internal RPC | tRPC (server actions) | Type-safe dashboard mutations |
| ORM | Drizzle | Lightweight, SQL-first, great types |
| DB | Postgres 16 | Versioned templates need relational integrity |
| Auth | better-auth | Self-hostable, owns its tables, no vendor lock |
| Queue | BullMQ + Redis 7 | Battle-tested for async jobs |
| Templating | Handlebars | Mature, safe, supports {{#if}} / {{#each}} |
| Validation | Zod + auto-generated JSON Schema | One source of truth |
Document rendering
| Layer | Choice | Rationale |
|---|---|---|
| PDF engine | Puppeteer + bundled Chromium | Full CSS fidelity |
| Serverless variant | @sparticuz/chromium |
Compatible with Lambda/Vercel functions if needed |
| HTML templating | Handlebars with safe helpers | format-date, format-currency, eq, gt, lt, each |
| Storage SDK | @aws-sdk/client-s3 |
S3-compatible against MinIO (dev) / R2 (prod) |
Embed mode (v0.2)
| Layer | Choice | Rationale |
|---|---|---|
| JWT signing | EdDSA (Ed25519) | Asymmetric, fast, modern |
| JWT library | jose |
Standards-compliant, well-maintained |
| postMessage protocol | hand-rolled in @craftkit/embed |
Tight control, no transitive deps |
| Iframe sandbox | allow-scripts allow-forms allow-same-origin |
Narrowest viable |
AI (v0.2 capstone)
| Layer | Choice | Rationale |
|---|---|---|
| Provider abstraction | Vercel AI SDK | Multi-provider, streaming, structured output |
| Default models | OpenAI gpt-4.1-mini for fast, gpt-4.1 for high quality |
Stable cost/quality |
| Fallback | Anthropic claude-sonnet-4 |
Diversification |
| Output format | Tiptap JSON via structured-output | LLM-cannot-corrupt invariant |
Observability
| Layer | Choice | Rationale |
|---|---|---|
| Errors | Sentry | Standard, generous free tier |
| Product analytics | PostHog (self-hostable) | EU-friendly, owns data |
| Logs | Axiom or Better Stack | Cheaper than Datadog at our scale |
| Metrics | OpenTelemetry | Vendor-neutral |
Testing
| Kind | Tool |
|---|---|
| Unit | Vitest |
| Component | Vitest + Testing Library |
| E2E | Playwright |
| API contracts | Vitest with real DB (Docker) |
CI/CD
- GitHub Actions for CI
- Per-PR: typecheck, lint, unit, anti-residue scan
- Per-merge to main: integration tests, deploy preview
- Per-release tag: production deploy to Vercel + Railway
What we deliberately did NOT choose
| Rejected | Why |
|---|---|
| Express / Fastify | Next.js Route Handlers cover it without a second runtime |
| Mongo / DynamoDB | Versioned templates + audit trails want relational |
| MJML for emails | Tiptap-based email blocks = same builder for everything |
| Clerk | Vendor lock; better-auth gives the same DX self-hostable |
| Mux / Cloudinary | Out of scope for v0.1–v0.3 |
| Prisma | Heavier than Drizzle, slower codegen |
| ESLint + Prettier | Biome is one tool, faster, less config |
Last revised: 2026-05-02