Product Ready Standards
Production-ready quality standards and best practices for ProductReady - build it right the first time
Product Ready Standards
ProductReady is built to product-ready standards. This guide explains the quality practices baked into the boilerplate and how to maintain them as you build.
Why This Matters: Following these standards means fewer bugs, easier maintenance, better performance, and happier users. Ship quality, ship fast.
📋 Product Definition Standards
"Product Ready" starts before you write code. A well-defined product prevents wasted engineering effort.
1. Product Requirements (PRD)
Don't just "start coding". Define what you are building and why.
- Problem Statement: What pain point are you solving?
- User Stories: "As a [user], I want to [action] so that [benefit]"
- Success Metrics: How will you know it's working?
2. Ideal Customer Profile (ICP)
Know who you are building for.
- Demographics: Role, industry, company size
- Psychographics: Goals, fears, motivations
- Buying Trigger: What makes them look for a solution?
3. Marketing Strategy
Build with distribution in mind.
- Value Proposition: One sentence that explains your product
- Channels: Where do your users hang out?
- Launch Plan: How will you get your first 10 users?
✅ Product Ready Checklist
Use this comprehensive checklist to verify your application is truly "Product Ready" before launch. This mirrors the quality criteria from the monorepo's App Status table.
Legend: ✅ Required for 100% score | ⭐ Highly Recommended | 📝 Optional but adds value
1. Product & Design (The "Soul")
| Requirement | Verification Method | Description |
|---|---|---|
| Product Specs | File Check: apps/XXX/spec/*.md | Required: product-design, icp-guide, marketing-guide, onboarding-story, design-system, vi |
| Visual Identity | Visual Check: Brand Consistency | Brand colors, typography, and voice defined and consistent |
| Design System | Visual Check: UI Consistency | Tokens for colors, spacing, radius defined and used |
| Theme Sync | Visual Check: Theme Toggle | CSS variables match vi.md and design-system.md |
| Brand Assets | Visual Check: Logo Display | Logo files (SVG/PNG) display correctly in light/dark modes |
2. User Experience (The "Body")
| Requirement | Verification Method | Description |
|---|---|---|
| Landing Page | URL Check: / | High-converting landing page with clear value prop |
| Auth Experience | Interaction: Sign In/Up Flow | Polished OAuth buttons, magic links, and error states |
| Demo Mode | URL Check: ?demo=1 | Instant access for users without login/database |
| Dashboard | URL Check: /dashboard | Protected app layout with sidebar/navigation |
| Mobile Responsive | Visual Check: Resize Window | UI works perfectly on mobile devices (375px width) |
| Dark Mode | Visual Check: Toggle Theme | First-class dark mode support without glitches |
3. Assets & Metadata (The "Face")
| Requirement | Verification Method | Description |
|---|---|---|
| Favicon | URL Check | /favicon.ico loads correctly |
| App Icon | URL Check | /icon.svg (or png) loads correctly |
| Open Graph | URL Check | /opengraph-image generates correct social preview |
| Screenshots | File Check | public/screenshots/ contains HD marketing images |
| Manifest | URL Check | /manifest.webmanifest returns valid JSON |
4. Trust & Legal (The "Suit")
| Requirement | Verification Method | Description |
|---|---|---|
| About Page | URL Check | /about App Store-style product showcase page |
| Brand Guidelines | URL Check | /brand brand guidelines with logo, colors, typography |
| Privacy Policy | URL Check | /privacy page exists and is accessible |
| Terms of Service | URL Check | /terms page exists and is accessible |
| Contact/Support | Visual Check | Footer or help center provides contact method |
5. Engineering & Ops (The "Brain")
| Requirement | Verification Method | Description |
|---|---|---|
| Type Safety | Build Check | pnpm typecheck passes with no errors |
| SEO Sitemap | URL Check | /sitemap.xml returns valid XML |
| SEO Robots | URL Check | /robots.txt exists and correctly allows/disallows |
| AI Crawler | URL Check | /llms.txt crawler instructions for LLMs |
| Health Check | URL Check | /api/health returns JSON status |
6. Content & i18n (The "Voice")
| Requirement | Verification Method | Description |
|---|---|---|
| Documentation | URL Check | /docs or /help renders content correctly |
| Localization | URL Check | /en, /zh etc prefixes work (if multi-lang) |
| Translations | Visual Check | UI text changes correctly when switching language |
7. Capabilities & Integrations (The "Muscle")
| Capability | Verification Method | Description |
|---|---|---|
| Payments | Interaction Check | Can initiate checkout flow (even in test mode) |
| Open API | URL Check | /api/openapi or /api/docs accessible |
| MCP Gateway | URL Check | /sse/mcp endpoint connects successfully |
| Admin Panel | URL Check | /admin accessible to authorized users |
| Email Delivery | Interaction Check | Transactional emails (welcome, magic link) arrive |
8. Quality Assurance (The "Shield")
| Requirement | Verification Method | Description |
|---|---|---|
| E2E Tests | Command Check | pnpm test:e2e passes critical flows |
| Unit Tests | Command Check | pnpm test passes business logic tests |
| Linting | Command Check | pnpm lint passes with no warnings |
🎨 Design Product-Ready Standards
Design System Architecture
ProductReady follows a strict design hierarchy to ensure consistency:
- Brand Identity: Your colors, typography, and voice (The "Soul")
- Design Tokens: Semantic names for values (e.g.,
primary-color,spacing-md) - CSS Variables: The technical implementation in
global.css - Components: UI elements consuming variables
🚨 Component Library Integrity (KUI)
DO NOT modify packages/kui/* directly.
packages/kui is the shared foundation library. Modifying it risks breaking multiple applications.
Correct way to customize:
- Import the component:
import { Button } from 'kui/button'; - Create a wrapper in your app:
apps/XXX/src/components/ui/my-button.tsx - Apply custom styles via CSS variables or className props.
CSS Variables (No Hardcoding!)
Always use CSS variables for colors, spacing, etc:
// ✅ Good: Uses CSS variables
<div className="bg-primary text-primary-foreground">
<button className="bg-accent hover:bg-accent/90">
// ❌ Bad: Hardcoded colors
<div className="bg-blue-500 text-white">
<button className="bg-green-600 hover:bg-green-700">Dark Mode Support
All UI must work in dark mode:
/* global.css */
:root {
--background: 0 0% 100%; /* White */
--foreground: 222 47% 11%; /* Dark text */
}
.dark {
--background: 224 71% 4%; /* Dark bg */
--foreground: 213 31% 91%; /* Light text */
}Test dark mode: Toggle in browser or use ?theme=dark
Responsive Design
Mobile-first approach:
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{/* 1 column mobile, 2 tablet, 3 desktop */}
</div>Test on:
- iPhone (375px width)
- iPad (768px)
- Desktop (1280px+)
📊 Accessibility Standards
WCAG 2.1 AA Compliance
Minimum requirements:
✅ Color Contrast:
- Normal text: 4.5:1
- Large text (18px+): 3:1
- Test with WebAIM Contrast Checker
✅ Keyboard Navigation:
// All interactive elements must be keyboard accessible
<button onClick={handleClick}>Click Me</button> ✅
<div onClick={handleClick}>Click Me</div> ❌✅ Focus Indicators:
button:focus-visible {
outline: 2px solid hsl(var(--primary));
outline-offset: 2px;
}✅ Alt Text for Images:
<img src="/logo.png" alt="ProductReady Logo" />✅ Semantic HTML:
✅ <nav>, <main>, <article>, <button>
❌ <div className="nav">, <div onClick={...}>🔗 Demo Mode Standard
ProductReady includes demo mode (?demo=1) for testing without database:
// src/lib/demo.ts
export function isDemoMode(searchParams?: URLSearchParams): boolean {
return searchParams?.get('demo') === '1';
}
// In tRPC router
export const tasksRouter = createTRPCRouter({
list: publicProcedure
.input(z.object({ demo: z.boolean().optional() }))
.query(async ({ ctx, input }) => {
if (input.demo) {
return { tasks: getDemoTasks() }; // Mock data
}
return { tasks: await ctx.db.select().from(tasks) };
}),
});Benefits:
- ✅ Instant testing (no database setup)
- ✅ Demos and presentations
- ✅ UI development without backend
🎯 Lighthouse Scores (Target)
Run Lighthouse audit in Chrome DevTools:
Minimum scores:
- ⚡ Performance: 90+
- ♿ Accessibility: 95+
- ✅ Best Practices: 95+
- 🔍 SEO: 95+
Common issues and fixes:
| Issue | Fix |
|---|---|
| Large images | Use Next.js <Image> with optimization |
| Unused JavaScript | Code split with dynamic imports |
| Missing alt text | Add to all <img> tags |
| Missing meta tags | Add in layout.tsx metadata |
📝 Documentation Standards
Code Comments
Use TSDoc for functions and components:
/**
* Creates a new task with AI-generated description
* @param input - Task title and priority
* @returns Created task with generated description
* @throws {Error} If AI service is unavailable
*/
export async function createTaskWithAI(
input: CreateTaskInput
): Promise<Task> {
const description = await generateDescription(input.title);
return db.insert(tasks).values({ ...input, description });
}README Updates
Keep your README current:
- ✅ Update when adding major features
- ✅ Keep setup instructions accurate
- ✅ Document new environment variables
- ✅ Add links to new documentation
🔄 Maintenance Standards
Dependency Updates
Monthly:
# Check for updates
pnpm outdated
# Update interactive
pnpm update -i
# Test after updating
pnpm typecheck
pnpm lint
pnpm test
pnpm buildSecurity Audits
Weekly:
# Check for vulnerabilities
pnpm audit
# Fix automatically
pnpm audit --fixChangelog Discipline
Create changelog for:
- ✅ New features
- ✅ Breaking changes
- ✅ Bug fixes (significant)
- ✅ Performance improvements
- ✅ Security updates
Format: apps/productready/changelog/YYYYMMDD-description.md
🎯 Core Quality Principles
1. Type Safety Everywhere
TypeScript strict mode catches bugs before they reach production.
// ✅ Good: Full type safety
interface CreateTaskInput {
title: string;
description?: string;
priority: 'low' | 'medium' | 'high' | 'urgent';
}
function createTask(input: CreateTaskInput): Promise<Task> {
// TypeScript knows exactly what input contains
return db.insert(tasks).values(input).returning();
}
// ❌ Bad: Using `any` loses all safety
function createTask(input: any) {
// No idea what input contains - errors at runtime!
return db.insert(tasks).values(input);
}Benefits:
- ✅ Auto-completion in your editor
- ✅ Catch typos and missing properties
- ✅ Refactoring is safe and easy
- ✅ Self-documenting code
2. Runtime Validation with Zod
TypeScript only checks at compile-time. Zod validates at runtime (when data comes from users, APIs, or databases).
import { z } from 'zod';
// Define schema
const CreateTaskSchema = z.object({
title: z.string().min(1).max(255),
description: z.string().optional(),
priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium'),
});
// Use in tRPC
export const tasksRouter = createTRPCRouter({
create: protectedProcedure
.input(CreateTaskSchema) // ✅ Validates input
.mutation(async ({ input }) => {
// input is guaranteed valid here
return db.insert(tasks).values(input);
}),
});What Zod Catches:
- Invalid data types (string vs number)
- Missing required fields
- Values outside allowed range
- Malformed emails, URLs, etc.
3. VO/DTO/PO Pattern
Separate concerns for data at different layers:
| Layer | Type | Purpose | Example |
|---|---|---|---|
| Database (PO) | Persistent Object | Raw database schema | Includes password hash, internal IDs |
| API (DTO) | Data Transfer Object | Input/output validation | User provides email, password |
| UI (VO) | View Object | Frontend display | Show user name, email, avatar |
// Database layer (PO) - Never expose directly!
const userPO = await db.query.users.findFirst({
where: eq(users.id, userId)
});
// Contains: passwordHash, stripeCustomerId, etc.
// API layer (DTO) - Validated input
const CreateUserDTO = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string(),
});
// UI layer (VO) - Safe to return to frontend
interface UserVO {
id: string;
email: string;
name: string;
avatar: string | null;
createdAt: string; // ISO string, not Date object
}
// Transform PO → VO before returning
function toUserVO(po: UserPO): UserVO {
return {
id: po.id,
email: po.email,
name: po.name,
avatar: po.image,
createdAt: po.createdAt.toISOString(),
};
}Security: Never return database objects directly to the frontend. Always transform through a VO.
🔒 Security Standards
Authentication & Authorization
ProductReady uses Better Auth with secure defaults:
✅ What's Already Configured:
- Password hashing (bcrypt)
- Session management (httpOnly cookies)
- CSRF protection
- Secure cookie flags (production only)
Your Responsibility:
// ✅ Always use protectedProcedure for sensitive operations
export const tasksRouter = createTRPCRouter({
delete: protectedProcedure // Requires authentication
.input(z.object({ id: z.number() }))
.mutation(async ({ ctx, input }) => {
// ctx.session.user is guaranteed to exist
const userId = ctx.session.user.id;
// ✅ Check ownership before deleting
const task = await db.query.tasks.findFirst({
where: eq(tasks.id, input.id),
});
if (!task || task.userId !== userId) {
throw new Error('Not authorized');
}
await db.delete(tasks).where(eq(tasks.id, input.id));
}),
});Environment Variables
Never commit secrets!
# ✅ Good: Use .env files (git ignored)
PG_DATABASE_URL="postgresql://..."
BETTER_AUTH_SECRET="..."
# ❌ Bad: Hardcoded in code
const secret = "super-secret-key-123"; // NEVER DO THIS!Prefix for public vars:
# ✅ Available in browser
NEXT_PUBLIC_APP_NAME="ProductReady"
# ✅ Server-only (secure)
PG_DATABASE_URL="postgresql://..."
STRIPE_SECRET_KEY="sk_live_..."📊 Performance Standards
Database Queries
Avoid N+1 queries - Use joins instead of loops:
// ❌ Bad: N+1 query (1 + N queries)
const tasks = await db.select().from(tasks);
for (const task of tasks) {
const user = await db.select().from(users)
.where(eq(users.id, task.userId)); // N queries!
}
// ✅ Good: Single query with join
const tasksWithUsers = await db
.select({
task: tasks,
user: users,
})
.from(tasks)
.leftJoin(users, eq(tasks.userId, users.id));Pagination
Always paginate large lists:
export const tasksRouter = createTRPCRouter({
list: protectedProcedure
.input(z.object({
page: z.number().min(1).default(1),
limit: z.number().min(1).max(100).default(20),
}))
.query(async ({ ctx, input }) => {
const offset = (input.page - 1) * input.limit;
const tasks = await ctx.db
.select()
.from(tasks)
.limit(input.limit)
.offset(offset);
return { tasks, page: input.page, limit: input.limit };
}),
});Connection Pooling
Critical for Vercel/serverless!
Use pooled database connections:
# Neon - use port 6543 (pooled) instead of 5432
PG_DATABASE_URL="postgresql://user:pass@host:6543/db"
# Supabase - use "connection pooling" string
PG_DATABASE_URL="postgresql://...pooler.supabase.com:6543/..."🧪 Testing Standards
E2E Tests (Playwright)
ProductReady includes smoke tests. Add more for critical flows:
// tests/e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => {
test('user can sign up', async ({ page }) => {
await page.goto('/signup');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.fill('input[name="name"]', 'Test User');
await page.click('button[type="submit"]');
// Should redirect to dashboard
await expect(page).toHaveURL('/dashboard');
});
});Run tests:
pnpm test:e2e # Headless
pnpm test:e2e:ui # Interactive modeUnit Tests (Vitest)
Test business logic and utilities:
// src/lib/utils.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency } from './utils';
describe('formatCurrency', () => {
it('formats USD correctly', () => {
expect(formatCurrency(1000, 'USD')).toBe('$1,000.00');
});
it('handles zero', () => {
expect(formatCurrency(0, 'USD')).toBe('$0.00');
});
});📐 Code Product-Ready Standards
Linting & Formatting
ProductReady uses Biome (faster than ESLint + Prettier):
# Format and lint
pnpm lint
# Auto-fix issues
pnpm lint --writePre-commit hooks: Configured with Lefthook (runs on every commit)
TypeScript Strictness
All apps must have strict mode enabled:
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}Component Patterns
Keep components small and focused:
// ✅ Good: Single responsibility
function TaskCard({ task }: { task: Task }) {
return (
<div className="card">
<h3>{task.title}</h3>
<p>{task.description}</p>
</div>
);
}
function TaskList({ tasks }: { tasks: Task[] }) {
return (
<div className="grid gap-4">
{tasks.map(task => <TaskCard key={task.id} task={task} />)}
</div>
);
}
// ❌ Bad: Too many responsibilities
function TaskPage() {
// Fetching, filtering, sorting, rendering all in one component
// 500 lines of code...
}🚀 Production Standards
Health Check Endpoint
ProductReady includes /api/health for monitoring:
// src/app/api/health/route.ts
export async function GET() {
return Response.json({
status: 'healthy',
timestamp: new Date().toISOString(),
service: 'productready',
version: process.env.VERSION || 'unknown',
buildNumber: process.env.NEXT_PUBLIC_BUILD_NUMBER || '0',
});
}Use for:
- Uptime monitoring (UptimeRobot, Better Uptime)
- Load balancer health checks
- Deployment verification
Error Tracking
Recommended: Sentry or similar
// src/app/layout.tsx
import * as Sentry from '@sentry/nextjs';
if (process.env.NODE_ENV === 'production') {
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
});
}📝 Documentation Standards
Code Comments
Use TSDoc for functions and components:
/**
* Creates a new task with AI-generated description
* @param input - Task title and priority
* @returns Created task with generated description
* @throws {Error} If AI service is unavailable
*/
export async function createTaskWithAI(
input: CreateTaskInput
): Promise<Task> {
const description = await generateDescription(input.title);
return db.insert(tasks).values({ ...input, description });
}README Updates
Keep your README current:
- ✅ Update when adding major features
- ✅ Keep setup instructions accurate
- ✅ Document new environment variables
- ✅ Add links to new documentation
🔄 Maintenance Standards
Dependency Updates
Monthly:
# Check for updates
pnpm outdated
# Update interactive
pnpm update -i
# Test after updating
pnpm typecheck
pnpm lint
pnpm test
pnpm buildSecurity Audits
Weekly:
# Check for vulnerabilities
pnpm audit
# Fix automatically
pnpm audit --fixChangelog Discipline
Create changelog for:
- ✅ New features
- ✅ Breaking changes
- ✅ Bug fixes (significant)
- ✅ Performance improvements
- ✅ Security updates
Format: apps/productready/changelog/YYYYMMDD-description.md
🎯 Lighthouse Scores (Target)
Run Lighthouse audit in Chrome DevTools:
Minimum scores:
- ⚡ Performance: 90+
- ♿ Accessibility: 95+
- ✅ Best Practices: 95+
- 🔍 SEO: 95+
Common issues and fixes:
| Issue | Fix |
|---|---|
| Large images | Use Next.js <Image> with optimization |
| Unused JavaScript | Code split with dynamic imports |
| Missing alt text | Add to all <img> tags |
| Missing meta tags | Add in layout.tsx metadata |
🔗 Demo Mode Standard
ProductReady includes demo mode (?demo=1) for testing without database:
// src/lib/demo.ts
export function isDemoMode(searchParams?: URLSearchParams): boolean {
return searchParams?.get('demo') === '1';
}
// In tRPC router
export const tasksRouter = createTRPCRouter({
list: publicProcedure
.input(z.object({ demo: z.boolean().optional() }))
.query(async ({ ctx, input }) => {
if (input.demo) {
return { tasks: getDemoTasks() }; // Mock data
}
return { tasks: await ctx.db.select().from(tasks) };
}),
});Benefits:
- ✅ Instant testing (no database setup)
- ✅ Demos and presentations
- ✅ UI development without backend
📚 Further Reading
- TypeScript Best Practices
- Zod Documentation
- Web Accessibility Guidelines
- Next.js Performance
- Security Best Practices
🆘 Quality Issues?
If you find quality issues in ProductReady:
- Report: Create GitHub Issue
- Fix: Submit PR with fix + tests
- Document: Update this guide if needed
Remember: Quality is not a one-time thing. It's a continuous practice. Build it into your workflow! 🚀