- 修复关键词词云页面和 API 路由中的 async params 处理 - 将 params 和 searchParams 正确声明为 Promise 类型并添加 await - 在 CLAUDE.md 中添加 Next.js 15 Breaking Change 说明 - 添加 ESLint/Prettier 配置文档 - 添加 Next.js 15 页面创建指南 - 补充故障排查部分的相关错误处理说明 Co-Authored-By: Claude (glm-4.7) <noreply@anthropic.com>
23 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Development Commands
Build & Run
pnpm dev # Start development server (Next.js 15)
pnpm build # Build for production
pnpm start # Start production server
pnpm lint # Run ESLint
Testing
pnpm test # Run Vitest unit tests
pnpm test:e2e # Run Playwright E2E tests
Database
pnpm prisma migrate dev # Run database migrations
pnpm prisma migrate dev --name init # Create initial migration
pnpm prisma db seed # Seed database (uses ts-node)
pnpm prisma studio # Open Prisma Studio for database inspection
Architecture Overview
This is a Next.js 15 multilingual AI project navigation website using the App Router architecture with the following key components:
i18n Architecture (next-intl)
- Locales:
zh(default) anden - Route pattern:
/{locale}/path(always prefixed with locale) - Middleware:
src/middleware.tshandles locale detection and routing - i18n config:
src/i18n/request.tsloads locale messages fromsrc/messages/{locale}.json - Messages: Translation files at
src/messages/zh.jsonandsrc/messages/en.json
App Router Structure
src/app/
├── [locale]/ # Locale-scoped routes
│ ├── page.tsx # Home page
│ ├── projects/ # Projects listing and details
│ │ ├── page.tsx # Projects list
│ │ └── [id]/ # Individual project details (slug-based)
│ └── layout.tsx # Locale layout (header, footer)
├── api/ # API routes (no locale prefix)
│ └── webhook/projects/route.ts # Webhook for project ingestion
└── layout.tsx # Root layout
Database (Prisma + PostgreSQL)
- Provider: Neon (serverless PostgreSQL)
- Schema:
prisma/schema.prismadefines models:Project,Tag,ExternalLink,ProjectTag - Enums:
ProjectStatus(ACTIVE/ARCHIVED),LinkType(WEBSITE/GITHUB/HUGGINGFACE/PAPER) - Client singleton:
src/lib/prisma.tsexports Prisma client instance - Multilingual fields: Most models have
name/nameEn,description/descriptionEn,content/contentEnpairs - Cascade deletions:
ExternalLinkandProjectTaguseonDelete: Cascade- deleting a project automatically cleans up its links and tag connections - Key constraints:
Project.slug: UniqueTag.name: Unique (tag names are globally unique)Tag.slug: UniqueExternalLink:@@unique([projectId, url])(each project can't have duplicate URLs)
- Indexes:
idx_project_status_createdAt,idx_project_slug,idx_tag_slug,idx_link_projectId,idx_link_type,idx_link_type_url(composite for efficient URL-based deduplication)
Neon Database Setup
- Dashboard: https://console.neon.tech
- Connection String Format:
postgres://[user]:[password]@[host]/[database]?sslmode=require - Vercel Integration: Set
DATABASE_URLenvironment variable in Vercel Dashboard (do NOT usevercel.jsonenv references) - Free Tier: 0.5GB storage, 300 hours compute/month
- Run Migrations: After deployment, run
pnpm prisma db pushor use Neon's SQL Editor to create tables
Webhook Deduplication Strategy
The webhook at src/app/api/webhook/projects/route.ts implements a multi-level deduplication strategy to prevent duplicate projects:
- GitHub URL exact match (highest priority) - via
ExternalLinktable usingidx_link_type_urlindex - Website URL exact match - via
ExternalLinktable usingidx_link_type_urlindex - slug match (fallback) - via
Project.slugfield
When updating an existing project, the webhook:
- Updates all project fields (name, description, content, status, source)
- Replaces all tags (deletes old
ProjectTagconnections viaProjectTagtable, creates new ones) - Replaces all links (deletes old
ExternalLinkentries, creates new ones)
Tag handling special case: Due to Tag.name unique constraint, tag upsert follows:
- First try to find existing tag by name
- If not found, try upsert by slug
- If slug conflicts, use the existing tag with that slug
Data Fetching (Server-Side)
- Location:
src/hooks/useProjects.ts(server functions, not React hooks) - Functions:
getProjects(),getProjectBySlug(),getAllTags(),getTagsWithProjectCounts() - Usage: Directly called in Server Components and route handlers
- Query transformation:
getProjects()andgetProjectBySlug()flatten theProjectTagjunction table structure to return tags directly - ISR: Project detail pages use
export const revalidate = 300(5 minutes) atsrc/app/[locale]/projects/[id]/page.tsx
Validation (Zod)
- Schemas:
src/lib/validations.tsdefines all Zod schemas ProjectInputSchema: Validates incoming project data (1-10 tags, 1-10 links required)WebhookPayloadSchema: Validates webhook requests with API key (1-100 projects per request)ProjectQuerySchema: Validates query parameters (search, tags, status, page, limit)
Styling (Tailwind CSS)
- Neo-brutalism design: Sharp corners (0px radius), bold borders (4px shadows), hard edges
- Theme colors:
- Primary: Gold (#FFD700)
- Secondary: Orange (#ff6f00)
- Background light: #F5F2EB, dark: #121212
- Surface light: #FFFFFF, dark: #1E1E1E
- Typography: Space Mono (headings), Inter (body)
- Dark mode: Class-based with
dark:prefix - Config:
tailwind.config.tsextends theme with custom colors, shadows, and animations
Content Rendering
- Markdown: Project content fields support Markdown via
react-markdown - Plugins:
rehype-raw,rehype-sanitize,rehype-shiki,remark-gfm - Usage:
ProjectDetailcomponent renderscontent/contentEnas Markdown
UI Components
- Radix UI primitives:
@radix-ui/react-slot,@radix-ui/react-navigation-menu,@radix-ui/react-dropdown-menu,@radix-ui/react-separator - Lucide React icons: Used throughout the app (package imports optimized via
experimental.optimizePackageImports) - Custom components:
src/components/organized by domainlayout/: Header, Footer, AnnouncementBarlocale/: LocaleSwitcherproject/: ProjectCard, ProjectList, ProjectDetail, ProjectSidebar, RelatedProjects, TagCloud, ExternalLinkCard, ShareButtons, MarkdownContent, GitHubBadges, GitHubTextStatsCardsearch/: SearchBarui/: Base UI components (buttons, cards, etc.)
Next.js Configuration
- next.config.js:
next-intlplugin wrapper for i18n- Image domains: localhost, *.anthropic.com, img.shields.io
- Lucide-react package import optimization
- tsconfig.json: ES2017 target, strict mode enabled, noUncheckedIndexedAccess enabled
- Testing: Vitest for unit tests, Playwright for E2E tests (configured but not extensively used yet)
Next.js 15 Breaking Change: Async Params
Critical: Next.js 15 changed params and searchParams to be async (Promise type).
- In page components:
params: Promise<{ locale: string }>andsearchParams: Promise<{ key?: string }> - In API routes:
{ params }: { params: Promise<{ id: string }> } - Always await these params before using them:
// ✅ Correct const { locale } = await params; const resolvedSearchParams = await searchParams; const quarter = resolvedSearchParams.quarter || 'default'; // ❌ Wrong (causes runtime errors in Next.js 15) const { locale } = params; // params is a Promise, not an object const quarter = searchParams.quarter;
Project Discovery System
项目发现系统是自动化探索和收录 AI 项目的核心功能,采用双 Agent 协作架构实现上下文隔离:
架构组件
-
自定义 Agents (
.claude/agents/):content-explorer-agent: 项目内容探索专家,批量探索项目并生成结构化数据- 使用
agent-browser子任务并行探索 GitHub 项目 - 应用严格的内容质量标准(客观描述、避免营销术语、不写入动态数据)
- 生成符合
ProjectInputSchema的 JSON 数据
- 使用
api-submitter-agent: API 提交专家,处理探索结果的提交和状态更新- 批量标记任务为 IN_PROGRESS
- 提交探索数据到完成 API
- 自动重试失败的提交(指数退避,最多3次)
-
Claude Commands (
.claude/commands/):/discover-projects: 主命令,协调探索和提交流程- 参数解析(任务数量、批次大小)
- 分批处理(默认每批3个任务)
- Agent 调度和进度显示
- 结果汇总和错误报告
-
API Endpoints (
src/app/api/discovery/):POST /api/discovery/tasks: 创建新的探索任务(支持批量)GET /api/discovery/tasks: 获取待处理任务列表(支持 status/limit/offset 过滤)PATCH /api/discovery/tasks/{id}: 更新任务状态POST /api/discovery/tasks/{id}/complete: 完成任务并提交项目数据GET /api/webhook/check-duplicates: 检查项目是否已存在(URL 去重)
-
Database Model:
ProjectDiscoveryTask: 任务追踪表- 状态: PENDING → IN_PROGRESS → COMPLETED/FAILED
- 原始数据:
sourceUrl,sourceType - 探索结果:
explorationData(JSON),explorationSummary - 错误处理:
errorMessage,retryCount,lastRetryAt - 索引:
idx_task_status_created,idx_task_source_url,idx_task_project_id
数据流转
用户输入 URL → 创建 PENDING 任务 → /discover-projects 命令
↓
分批获取任务(每批3个)
↓
Content Explorer Agent (并行探索) → 探索结果 JSON
↓
API Submitter Agent (提交到生产环境 API)
↓
更新任务状态 → COMPLETED/FAILED
质量标准
- 数据模板:
.claude/schemas/project-content-template.md - 描述要求: 清晰说明功能、突出价值、避免营销术语、10-500字
- 内容要求: 从 README 提取并重新组织、不机械翻译、符合中文表达习惯
- 链接要求: 必须包含 GITHUB 链接、所有链接可访问
- 标签要求: 1-10 个标签、技术/应用/状态分类
- 动态数据处理: Star/Fork 数量等动态数据不写入内容,使用 GitHub Badge 显示
环境变量
WEBHOOK_API_KEY: 生产环境 API 密钥(必需,用于认证)
使用示例
# 处理默认10个任务(每批3个)
/discover-projects
# 处理指定数量的任务
/discover-projects 5
# 自定义批次大小
/discover-projects 9 --batch=2
# 处理所有待处理任务
/discover-projects all --batch=5
Keyword Cloud System (季度 AI 热点词云)
功能: 自动化采集 Google Trends 数据,展示季度 AI 热点词汇词云。
数据流: n8n 工作流 → AI 清洗 → 规则匹配 → PostgreSQL → Next.js 前端
数据库表
Quarter: 季度元数据(quarter, title, titleEn, subtitle, subtitleEn, displayOrder, isActive)Keyword: 关键词数据(word, trendScore, description, visualConfig)VisualStyleRule: 视觉样式规则配置(name, minScore, maxScore, color, size, border, rotation)KeywordCloudErrorLog: 错误日志(quarter, keyword, errorType, errorMessage)
API 端点
GET /api/keyword-cloud/quarters: 获取季度列表(支持isActive过滤)GET /api/keyword-cloud/keywords/[quarter]: 获取指定季度的关键词GET /api/keyword-cloud/rules: 获取视觉样式规则配置POST /api/keyword-cloud/keywords: 批量写入关键词(n8n 使用,需 API Key 认证)GET /api/keyword-cloud/health: 健康检查端点(返回系统统计信息)
前端路由
/[locale]/keyword-cloud: 词云展示页面
前端组件
- Location:
src/app/[locale]/keyword-cloud/components/ CloudWord: 单个词汇组件(支持颜色、大小、边框、旋转、悬停弹出框)QuarterNavigator: 季度导航组件(前后切换)ProgressIndicator: 进度条组件(显示季度进度)KeywordCloud: 主容器组件(集成所有子组件,客户端组件)
国际化支持
- 消息键:
src/messages/{locale}.json中的keywordCloud命名空间 - 支持的字段:
metaTitle/metaDescription: SEO 元数据badge: 页面徽章文本title/titleHighlight: 主标题(支持高亮)subtitle: 副标题描述loading/loadFailed/retry: 加载状态文本hotKeyword: 热门词汇提示(支持{word}参数替换)
- 导航菜单:
navigation.keywordCloud键控制导航栏显示 - 添加新翻译: 更新
src/messages/zh.json和src/messages/en.json中的keywordCloud部分
数据访问层
- Location:
src/hooks/useKeywordCloud.ts(服务器端函数) - Functions:
getAllQuarters(): 获取所有季度列表getQuarterByQuarter(): 获取单个季度详情(含关键词计数)getKeywordsByQuarter(): 获取指定季度的所有关键词getVisualStyleRules(): 获取视觉样式规则upsertQuarter(): 创建或更新季度createKeywords(): 批量创建关键词logKeywordCloudError(): 记录错误日志
客户端 Hook
- Location:
src/hooks/useKeywordCloudClient.ts - Function:
useKeywordCloud(quarter)- 响应式获取季度关键词数据
n8n 工作流
- 配置文件:
n8n-workflows/keyword-cloud-workflow.json - 文档:
n8n-workflows/README.md - 流程:
- Schedule Trigger: 每季度末最后一天的 23:00 自动触发
- Calculate Quarter: 计算当前季度标识和时间范围
- Google Trends: 采集热门搜索词
- Extract Keywords: 提取关键词和热度分数
- Get Visual Rules: 获取视觉样式规则
- Match Visual Rules: 为关键词匹配视觉样式
- Send to API: 写入数据库
初始化数据
# 运行种子数据脚本(创建视觉规则和示例数据)
pnpm tsx scripts/seed-keyword-cloud.ts
测试 API
# 运行 API 测试脚本
set -a && source .env.local && set +a && npx tsx scripts/test-keyword-api.ts
环境变量
WEBHOOK_API_KEY: n8n 工作流使用的 API 密钥(必需)- n8n 环境变量(在 n8n 中设置):
API_URL: API 端点 URL(如http://localhost:3000)API_KEY: 与WEBHOOK_API_KEY相同
MCP Servers Usage (按需使用)
- context7: 不确定 API 用法时查阅最新文档
- chrome-devtools-mcp: 查看页面效果、调试 UI 修复 BUG
- web-search-prime: 默认联网搜索工具
- vision-mcp-server: 图片/视频理解
Git Commits
Git 提交信息遵循约定式提交格式(详见上方 Code Quality & Standards → Git Commit Conventions)。
Development Workflow
- Before viewing the page, check if there's already a project running at localhost:3000. If yes, access it directly; if no, then run
pnpm dev - The dev server runs with hot-reload enabled for fast iteration
- The middleware handles locale detection and routing automatically - no manual locale configuration needed
- When adding new translations, update both
src/messages/zh.jsonandsrc/messages/en.json - Database changes require running
pnpm prisma migrate devto update the schema - Always verify API responses with
WEBHOOK_API_KEYwhen testing webhook or discovery endpoints - Use
pnpm prisma studioto inspect database state during development
Common Development Patterns
Adding a New API Endpoint
- Create route file in
src/app/api/(e.g.,src/app/api/your-endpoint/route.ts) - Import validation schemas from
@/lib/validations - Use
crypto.timingSafeEqual()for API key authentication (see webhook route:36-43) - Implement partial success mode for batch operations (see webhook route:55-67)
- Return structured errors with
NextResponse.json()
Adding New Database Fields
- Update
prisma/schema.prismawith new fields - Run
pnpm prisma migrate dev --name your_migration_name - Update Zod schemas in
src/lib/validations.ts - Update TypeScript types in
src/hooks/useProjects.tsif needed - Regenerate Prisma client:
pnpm prisma generate
Creating Custom Agents
- Create agent file in
.claude/agents/your-agent.md - Define agent role, capabilities, and task instructions
- Reference existing agents (
content-explorer-agent,api-submitter-agent) as templates - Test via corresponding Claude Command in
.claude/commands/
Working with Multilingual Content
- All user-facing content should have both Chinese (
name,description) and English (nameEn,descriptionEn) versions - Use
src/messages/zh.jsonandsrc/messages/en.jsonfor UI translations - For project data, prefer Chinese as primary language with English as optional
- When creating content, avoid mechanical translation - write naturally for each locale
Creating New Pages (Next.js 15)
When adding new pages in the App Router, remember that params and searchParams are Promises:
// Page component structure
interface PageProps {
params: Promise<{ locale: string; id?: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}
export default async function MyPage({ params, searchParams }: PageProps) {
// MUST await params and searchParams
const { locale, id } = await params;
const { filter, sort } = await searchParams;
// Now you can use the values
// ...
}
// For generateMetadata
export async function generateMetadata({
params
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params;
// ...
}
For API routes with dynamic parameters:
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
// ...
}
Code Quality & Standards
TypeScript Configuration
- Strict mode enabled with additional safety flags:
noUncheckedIndexedAccess,noImplicitReturns,noFallthroughCasesInSwitch - Path alias:
@/*maps to./src/* - Target: ES2017 for modern browser support
ESLint Configuration
- Config:
.eslintrc.jsonextendsnext/core-web-vitalsandprettier - Console rules:
no-consolewarns onconsole.logbut allowsconsole.warnandconsole.error - Prettier integration:
eslint-config-prettierdisables conflicting ESLint rules - Prettier config:
.prettierrc.jsonfor code formatting
Validation & Security
- API Authentication: Webhook uses timing-safe comparison (
crypto.timingSafeEqual) to prevent timing attacks - Input Validation: All API inputs use Zod schemas with detailed error messages
- SQL Injection Prevention: Prisma ORM with parameterized queries
- Data Sanitization: Markdown content sanitized with
rehype-sanitizeplugin
Error Handling Patterns
- Webhook: Partial success mode - continues processing remaining projects even if individual projects fail
- Database: Unique constraints use try-catch with fallback logic (e.g., tag slug conflicts in webhook)
- Console: Use
console.warn()for operational logs,console.error()for errors
Git Commit Conventions
- Format:
<type>: <description>(type in lowercase Chinese: feat/fix/refactor/chore) - Types:
feat(新功能),fix(修复),refactor(重构),chore(杂项) - Examples:
feat: 新增项目发现任务系统fix: 修复 ESLint 警告refactor: 重构项目内容标准实现职责分离
Testing Strategy
Unit Tests (Vitest)
- Location: Test files co-located with source code (e.g.,
*.test.ts) - Run:
pnpm testfor all tests,pnpm test <pattern>for specific tests - Configuration: Vitest with
@testing-library/jest-dommatchers
E2E Tests (Playwright)
- Location:
tests/e2e/or co-located with features - Run:
pnpm test:e2eto execute all E2E tests - Usage: Focus on critical user journeys (project browsing, search, locale switching)
Performance Considerations
Database Optimization
- Index Strategy: Composite indexes on frequently queried fields (status+createdAt, type+url)
- N+1 Prevention: Batch queries for tags (see webhook route:94-98)
- Connection Pooling: Prisma client singleton pattern (
src/lib/prisma.ts)
Frontend Performance
- ISR: Project detail pages revalidated every 5 minutes (
revalidate = 300) - Package Optimization: Lucide-react imports optimized via
experimental.optimizePackageImports - Image Domains: Pre-configured for localhost, *.anthropic.com, img.shields.io
API Rate Limiting
- Webhook: Max 100 projects per request
- Discovery tasks: Max 50 tasks per batch creation
- All queries: Max 100 items per page (enforced via Zod schemas)
Troubleshooting
Common Issues
Database Connection Errors
- Verify
DATABASE_URLis set in.env.local - Check Neon dashboard for database status
- Run
pnpm prisma db pushto sync schema if needed
Webhook Authentication Failures
- Ensure
WEBHOOK_API_KEYis set and matches (32+ characters) - Check that API key is sent in request body
- Verify timing-safe comparison is used (never log raw API keys)
Missing Translations
- All new UI text must be added to both
src/messages/zh.jsonandsrc/messages/en.json - Missing keys will display as
missing_key_namein the UI - Test both locales (switch via URL path
/zh/or/en/)
Build Errors After Schema Changes
- Run
pnpm prisma generateto regenerate Prisma client - Restart dev server after schema changes
- Check for TypeScript errors in generated types
Next.js 15 Async Params Errors
- If you see errors like
params.locale is undefinedorCannot read properties of undefined, you likely forgot toawaitthe params - Check that all page components properly await:
const { locale } = await params; - Check that all API routes properly await:
const { id } = await params; - See "Next.js Configuration → Next.js 15 Breaking Change" above for examples
Discovery Task Failures
- Check
src/app/api/discovery/lib/discovery-service.ts:16-80for deduplication logic - Verify
WEBHOOK_API_KEYis set for api-submitter-agent - Use
pnpm prisma studioto inspect task status and error messages - Check browser console for agent-browser errors (requires chrome-devtools-mcp)
Debugging Tips
-
Enable Prisma Query Logging:
// In src/lib/prisma.ts export const prisma = new PrismaClient({ log: ['query', 'error', 'warn'], }) -
Check Database State:
pnpm prisma studio # Opens at http://localhost:5555 -
Test API Endpoints:
# Create discovery task curl -X POST http://localhost:3000/api/discovery/tasks \ -H "Content-Type: application/json" \ -d '{"apiKey":"YOUR_KEY","tasks":[{"sourceUrl":"https://github.com/user/repo"}]}' -
Validate Request Payloads:
- All schemas are in
src/lib/validations.ts - Check schema errors in API response
detailsfield - Ensure all required fields are present
- All schemas are in
Environment Variables Checklist
For local development, ensure these are set in .env.local:
# Database (Neon PostgreSQL)
DATABASE_URL="postgres://[user]:[password]@[host]/[database]?sslmode=require"
# Webhook API Key (32+ characters)
WEBHOOK_API_KEY="your-secret-api-key-min-32-chars"
For production deployment (Vercel):
- Set
DATABASE_URLin Vercel Dashboard (Environment Variables) - Set
WEBHOOK_API_KEYin Vercel Dashboard - Run
pnpm prisma db pushafter first deployment to create tables - Do NOT use
vercel.jsonfor environment variables