ProductReadyProductReady

Product Ready 标准

ProductReady 的产品就绪质量标准和最佳实践 - 第一次就做对

Product Ready 标准

ProductReady 按照 product-ready 标准构建。本指南解释了样板代码中融入的质量实践以及如何在构建过程中保持它们。

为什么这很重要:遵循这些标准意味着更少的错误、更容易维护、更好的性能和更快乐的用户。交付质量,快速交付。


📋 产品定义标准

"Product Ready" 在你编写代码之前就开始了。 定义良好的产品可以防止浪费工程工作。

1. 产品需求 (PRD)

不要只是"开始编码"。定义你正在构建什么以及为什么

  • 问题陈述:你正在解决什么痛点?
  • 用户故事:"作为[用户],我想[行动],以便[好处]"
  • 成功指标:你如何知道它是否有效?

2. 理想客户画像 (ICP)

知道你为谁构建

  • 人口统计:角色、行业、公司规模
  • 心理特征:目标、恐惧、动机
  • 购买触发因素:什么让他们寻找解决方案?

3. 营销策略

构建时考虑到分发。

  • 价值主张:一句话解释你的产品
  • 渠道:你的用户在哪里?
  • 发布计划:你将如何获得前 10 个用户?

✅ Product Ready 检查清单

在发布之前,请使用此综合清单验证您的应用程序是否真正"Product Ready"。这反映了 monorepo 中 App Status 表格的质量标准。

图例:✅ 100% 评分必需 | ⭐ 强烈推荐 | 📝 可选但有价值

1. 产品与设计 ("灵魂")

要求验证方法描述
产品规格文件检查: apps/XXX/spec/*.md必需: product-design, icp-guide, marketing-guide, onboarding-story, design-system, vi
视觉识别视觉检查品牌颜色和排版在所有页面保持一致
设计系统代码检查无硬编码颜色/间距;使用 global.css 中的 CSS 变量
主题同步视觉检查亮色/暗色模式切换完美无故障
品牌资产URL 检查/logo.svg/brand/ 资产可访问

2. 用户体验 ("身体")

要求验证方法描述
落地页视觉检查首页在 5 秒内清晰阐述价值主张
认证体验交互检查注册/登录流程顺畅,有错误处理
演示模式URL 检查通过 ?demo=1 即时访问应用,无需登录
仪表板视觉检查登录后加载带有侧边栏/导航的受保护布局
移动端响应视觉检查布局在移动端 (375px 宽度) 适配正确

3. 资产与元数据 ("面孔")

要求验证方法描述
FaviconURL 检查/favicon.ico 加载正确
应用图标URL 检查/icon.svg (或 png) 加载正确
Open GraphURL 检查/opengraph-image 生成正确的社交预览
截图文件检查public/screenshots/ 包含高清营销图片
ManifestURL 检查/manifest.webmanifest 返回有效的 JSON

4. 信任与法律 ("西装")

要求验证方法描述
关于页面URL 检查/about App Store 风格的产品展示页
品牌指南URL 检查/brand 品牌指南,包含 logo、颜色、字体规范
隐私政策URL 检查/privacy 页面存在且可访问
服务条款URL 检查/terms 页面存在且可访问
联系/支持视觉检查页脚或帮助中心提供联系方式

5. 工程与运维 ("大脑")

要求验证方法描述
类型安全构建检查pnpm typecheck 通过且无错误
SEO 站点地图URL 检查/sitemap.xml 返回有效的 XML
SEO RobotsURL 检查/robots.txt 存在并正确允许/禁止爬虫
AI 爬虫URL 检查/llms.txt 针对 LLM 的爬虫指令
健康检查URL 检查/api/health 返回 JSON 状态

6. 内容与国际化 ("声音")

要求验证方法描述
文档URL 检查/docs/help 正确渲染内容
本地化URL 检查/en, /zh 等前缀工作正常 (如果支持多语言)
翻译视觉检查切换语言时 UI 文本正确变更

7. 能力与集成 ("肌肉")

能力验证方法描述
支付能力交互检查可以启动结账流程 (即使在测试模式下)
开放 APIURL 检查/api/openapi/api/docs 可访问
MCP 网关URL 检查/sse/mcp 端点连接成功
管理后台URL 检查/admin 对授权用户可访问
邮件投递交互检查事务性邮件 (欢迎、魔术链接) 到达收件箱

8. 质量保证 ("盾牌")

要求验证方法描述
E2E 测试命令检查pnpm test:e2e 通过关键流程
单元测试命令检查pnpm test 通过业务逻辑测试
代码检查命令检查pnpm lint 通过且无警告

🎨 设计质量标准

设计系统架构

ProductReady 遵循严格的设计层次结构以确保一致性:

  1. 品牌识别:你的颜色、排版和声音 ("灵魂")
  2. 设计令牌:值的语义名称(例如 primary-color, spacing-md
  3. CSS 变量global.css 中的技术实现
  4. 组件:使用变量的 UI 元素

🚨 组件库完整性 (KUI)

切勿直接修改 packages/kui/*

packages/kui 是共享的基础库。修改它可能会导致多个应用程序崩溃。

正确的定制方式

  1. 导入组件:import { Button } from 'kui/button';
  2. 在应用中创建包装器:apps/XXX/src/components/ui/my-button.tsx
  3. 通过 CSS 变量或 className 属性应用自定义样式。

CSS 变量(不要硬编码!)

始终使用 CSS 变量表示颜色、间距等:

// ✅ 好:使用 CSS 变量
<div className="bg-primary text-primary-foreground">
<button className="bg-accent hover:bg-accent/90">

// ❌ 差:硬编码颜色
<div className="bg-blue-500 text-white">
<button className="bg-green-600 hover:bg-green-700">

深色模式支持

所有 UI 必须在深色模式下工作

/* global.css */
:root {
  --background: 0 0% 100%;  /* 白色 */
  --foreground: 222 47% 11%;  /* 深色文本 */
}

.dark {
  --background: 224 71% 4%;  /* 深色背景 */
  --foreground: 213 31% 91%;  /* 浅色文本 */
}

测试深色模式:在浏览器中切换或使用 ?theme=dark

响应式设计

移动优先方法

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
  {/* 移动端 1 列,平板 2 列,桌面 3 列 */}
</div>

测试平台

  • iPhone (375px 宽度)
  • iPad (768px)
  • 桌面 (1280px+)

📊 可访问性标准

WCAG 2.1 AA 合规

最低要求

颜色对比度

键盘导航

// 所有交互元素必须可通过键盘访问
<button onClick={handleClick}>点击我</button>  ✅
<div onClick={handleClick}>点击我</div>        ❌

焦点指示器

button:focus-visible {
  outline: 2px solid hsl(var(--primary));
  outline-offset: 2px;
}

图片替代文本

<img src="/logo.png" alt="ProductReady Logo" />

语义 HTML

<nav>, <main>, <article>, <button>
❌ <div className="nav">, <div onClick={...}>

🔗 演示模式标准

ProductReady 包括演示模式 (?demo=1) 用于无需数据库的测试:

// src/lib/demo.ts
export function isDemoMode(searchParams?: URLSearchParams): boolean {
  return searchParams?.get('demo') === '1';
}

// 在 tRPC 路由器中
export const tasksRouter = createTRPCRouter({
  list: publicProcedure
    .input(z.object({ demo: z.boolean().optional() }))
    .query(async ({ ctx, input }) => {
      if (input.demo) {
        return { tasks: getDemoTasks() };  // 模拟数据
      }
      
      return { tasks: await ctx.db.select().from(tasks) };
    }),
});

好处

  • ✅ 即时测试(无需数据库设置)
  • ✅ 演示和展示
  • ✅ 无需后端的 UI 开发

🎯 Lighthouse 分数 (目标)

在 Chrome DevTools 中运行 Lighthouse 审计:

最低分数

  • 性能: 90+
  • 可访问性: 95+
  • 最佳实践: 95+
  • 🔍 SEO: 95+

常见问题和修复

问题修复
大图片使用 Next.js <Image> 进行优化
未使用的 JavaScript使用动态导入进行代码拆分
缺少替代文本添加到所有 <img> 标签
缺少元标签在 layout.tsx 元数据中添加

📝 文档标准

代码注释

为函数和组件使用 TSDoc:

/**
 * 创建带有 AI 生成描述的新任务
 * @param input - 任务标题和优先级
 * @returns 带有生成描述的创建任务
 * @throws {Error} 如果 AI 服务不可用
 */
export async function createTaskWithAI(
  input: CreateTaskInput
): Promise<Task> {
  const description = await generateDescription(input.title);
  return db.insert(tasks).values({ ...input, description });
}

README 更新

保持 README 最新:

  • ✅ 添加主要功能时更新
  • ✅ 保持设置说明准确
  • ✅ 记录新的环境变量
  • ✅ 添加到新文档的链接

🔄 维护标准

依赖更新

每月

# 检查更新
pnpm outdated

# 交互式更新
pnpm update -i

# 更新后测试
pnpm typecheck
pnpm lint
pnpm test
pnpm build

安全审计

每周

# 检查漏洞
pnpm audit

# 自动修复
pnpm audit --fix

变更日志纪律

为以下创建变更日志

  • ✅ 新功能
  • ✅ 破坏性更改
  • ✅ 错误修复(重要)
  • ✅ 性能改进
  • ✅ 安全更新

格式apps/productready/changelog/YYYYMMDD-description.md


🎯 核心质量原则

1. 处处类型安全

TypeScript 严格模式在代码投入生产之前捕获错误。

// ✅ 好:完全类型安全
interface CreateTaskInput {
  title: string;
  description?: string;
  priority: 'low' | 'medium' | 'high' | 'urgent';
}

function createTask(input: CreateTaskInput): Promise<Task> {
  // TypeScript 确切知道 input 包含什么
  return db.insert(tasks).values(input).returning();
}

// ❌ 差:使用 `any` 失去所有安全性
function createTask(input: any) {
  // 不知道 input 包含什么 - 运行时错误!
  return db.insert(tasks).values(input);
}

好处

  • ✅ 编辑器中的自动完成
  • ✅ 捕获拼写错误和缺失属性
  • ✅ 重构安全且简单
  • ✅ 自我文档化的代码

2. 使用 Zod 进行运行时验证

TypeScript 只在编译时检查。Zod 在运行时验证(当数据来自用户、API 或数据库时)。

import { z } from 'zod';

// 定义架构
const CreateTaskSchema = z.object({
  title: z.string().min(1).max(255),
  description: z.string().optional(),
  priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium'),
});

// 在 tRPC 中使用
export const tasksRouter = createTRPCRouter({
  create: protectedProcedure
    .input(CreateTaskSchema)  // ✅ 验证输入
    .mutation(async ({ input }) => {
      // 这里的 input 保证是有效的
      return db.insert(tasks).values(input);
    }),
});

Zod 捕获的内容

  • 无效的数据类型(字符串 vs 数字)
  • 缺少必需字段
  • 超出允许范围的值
  • 格式错误的电子邮件、URL 等

3. VO/DTO/PO 模式

在不同层的数据中分离关注点

类型目的示例
数据库 (PO)持久对象原始数据库架构包括密码哈希、内部 ID
API (DTO)数据传输对象输入/输出验证用户提供邮箱、密码
UI (VO)视图对象前端显示显示用户名、邮箱、头像
// 数据库层 (PO) - 永远不要直接暴露!
const userPO = await db.query.users.findFirst({ 
  where: eq(users.id, userId) 
});
// 包含: passwordHash, stripeCustomerId 等

// API 层 (DTO) - 验证输入
const CreateUserDTO = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  name: z.string(),
});

// UI 层 (VO) - 可以安全地返回到前端
interface UserVO {
  id: string;
  email: string;
  name: string;
  avatar: string | null;
  createdAt: string;  // ISO 字符串,不是 Date 对象
}

// 返回之前将 PO → VO 转换
function toUserVO(po: UserPO): UserVO {
  return {
    id: po.id,
    email: po.email,
    name: po.name,
    avatar: po.image,
    createdAt: po.createdAt.toISOString(),
  };
}

安全性:永远不要将数据库对象直接返回到前端。始终通过 VO 转换。


🔒 安全标准

认证和授权

ProductReady 使用具有安全默认设置的 Better Auth

已配置的内容

  • 密码哈希 (bcrypt)
  • 会话管理 (httpOnly cookies)
  • CSRF 保护
  • 安全 cookie 标志(仅生产环境)

你的责任

// ✅ 始终对敏感操作使用 protectedProcedure
export const tasksRouter = createTRPCRouter({
  delete: protectedProcedure  // 需要身份验证
    .input(z.object({ id: z.number() }))
    .mutation(async ({ ctx, input }) => {
      // ctx.session.user 保证存在
      const userId = ctx.session.user.id;
      
      // ✅ 删除前检查所有权
      const task = await db.query.tasks.findFirst({
        where: eq(tasks.id, input.id),
      });
      
      if (!task || task.userId !== userId) {
        throw new Error('未授权');
      }
      
      await db.delete(tasks).where(eq(tasks.id, input.id));
    }),
});

环境变量

永远不要提交秘密!

# ✅ 好:使用 .env 文件(git 忽略)
PG_DATABASE_URL="postgresql://..."
BETTER_AUTH_SECRET="..."

# ❌ 差:代码中硬编码
const secret = "super-secret-key-123";  // 永远不要这样做!

公共变量的前缀

# ✅ 在浏览器中可用
NEXT_PUBLIC_APP_NAME="ProductReady"

# ✅ 仅服务器(安全)
PG_DATABASE_URL="postgresql://..."
STRIPE_SECRET_KEY="sk_live_..."

📊 性能标准

数据库查询

避免 N+1 查询 - 使用连接而不是循环:

// ❌ 差:N+1 查询 (1 + N 个查询)
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 个查询!
}

// ✅ 好:使用连接的单个查询
const tasksWithUsers = await db
  .select({
    task: tasks,
    user: users,
  })
  .from(tasks)
  .leftJoin(users, eq(tasks.userId, users.id));

分页

始终分页大列表:

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 };
    }),
});

连接池

对 Vercel/无服务器至关重要!

使用池化数据库连接:

# Neon - 使用端口 6543(池化)而不是 5432
PG_DATABASE_URL="postgresql://user:pass@host:6543/db"

# Supabase - 使用"连接池"字符串
PG_DATABASE_URL="postgresql://...pooler.supabase.com:6543/..."

🧪 测试标准

E2E 测试 (Playwright)

ProductReady 包括冒烟测试。为关键流程添加更多:

// tests/e2e/auth.spec.ts
import { test, expect } from '@playwright/test';

test.describe('身份验证', () => {
  test('用户可以注册', 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"]', '测试用户');
    
    await page.click('button[type="submit"]');
    
    // 应该重定向到仪表板
    await expect(page).toHaveURL('/dashboard');
  });
});

运行测试

pnpm test:e2e       # 无头模式
pnpm test:e2e:ui    # 交互模式

单元测试 (Vitest)

测试业务逻辑和实用程序:

// src/lib/utils.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency } from './utils';

describe('formatCurrency', () => {
  it('正确格式化 USD', () => {
    expect(formatCurrency(1000, 'USD')).toBe('$1,000.00');
  });
  
  it('处理零', () => {
    expect(formatCurrency(0, 'USD')).toBe('$0.00');
  });
});

📐 代码质量标准

代码检查和格式化

ProductReady 使用 Biome(比 ESLint + Prettier 更快):

# 格式化和检查
pnpm lint

# 自动修复问题
pnpm lint --write

预提交钩子:使用 Lefthook 配置(每次提交时运行)

TypeScript 严格性

所有应用必须启用严格模式:

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}

组件模式

保持组件小而专注

// ✅ 好:单一职责
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>
  );
}

// ❌ 差:太多职责
function TaskPage() {
  // 在一个组件中获取、过滤、排序、渲染
  // 500 行代码...
}

🚀 生产标准

健康检查端点

ProductReady 包括用于监控的 /api/health

// 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',
  });
}

用于

  • 正常运行时间监控(UptimeRobot、Better Uptime)
  • 负载均衡器健康检查
  • 部署验证

错误跟踪

推荐:Sentry 或类似工具

// 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,
  });
}

On this page