diff --git a/CLAUDE.md b/CLAUDE.md index 08201ca..1b0b541 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -135,6 +135,22 @@ When updating an existing project, the webhook: - **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 }>` and `searchParams: Promise<{ key?: string }>` +- In API routes: `{ params }: { params: Promise<{ id: string }> }` +- **Always await these params before using them**: + ```typescript + // ✅ 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 协作架构**实现上下文隔离: @@ -233,10 +249,23 @@ API Submitter Agent (提交到生产环境 API) - `/[locale]/keyword-cloud`: 词云展示页面 #### 前端组件 +- **Location**: `src/app/[locale]/keyword-cloud/components/` - `CloudWord`: 单个词汇组件(支持颜色、大小、边框、旋转、悬停弹出框) - `QuarterNavigator`: 季度导航组件(前后切换) - `ProgressIndicator`: 进度条组件(显示季度进度) -- `KeywordCloud`: 主容器组件(集成所有子组件) +- `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` (服务器端函数) @@ -332,6 +361,47 @@ Git 提交信息遵循约定式提交格式(详见上方 Code Quality & Standa - 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**: + +```typescript +// 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: +```typescript +export async function GET( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + const { id } = await params; + // ... +} +``` + ## Code Quality & Standards ### TypeScript Configuration @@ -339,6 +409,12 @@ Git 提交信息遵循约定式提交格式(详见上方 Code Quality & Standa - Path alias: `@/*` maps to `./src/*` - Target: ES2017 for modern browser support +### ESLint Configuration +- **Config**: `.eslintrc.json` extends `next/core-web-vitals` and `prettier` +- **Console rules**: `no-console` warns on `console.log` but allows `console.warn` and `console.error` +- **Prettier integration**: `eslint-config-prettier` disables conflicting ESLint rules +- **Prettier config**: `.prettierrc.json` for 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 @@ -411,6 +487,12 @@ Git 提交信息遵循约定式提交格式(详见上方 Code Quality & Standa - 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 undefined` or `Cannot read properties of undefined`, you likely forgot to `await` the 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-80` for deduplication logic - Verify `WEBHOOK_API_KEY` is set for api-submitter-agent diff --git a/src/app/[locale]/keyword-cloud/page.tsx b/src/app/[locale]/keyword-cloud/page.tsx index 93024e7..56b314a 100644 --- a/src/app/[locale]/keyword-cloud/page.tsx +++ b/src/app/[locale]/keyword-cloud/page.tsx @@ -2,12 +2,8 @@ import { KeywordCloud } from "./components/KeywordCloud"; import { getTranslations } from "next-intl/server"; interface PageProps { - params?: Promise<{ - locale?: string | string[]; - }>; - searchParams?: Promise<{ - quarter?: string | string[]; - }>; + params: Promise<{ locale: string }>; + searchParams: Promise<{ quarter?: string }>; } export async function generateMetadata({ params }: PageProps) { @@ -23,47 +19,10 @@ export async function generateMetadata({ params }: PageProps) { } export default async function KeywordCloudPage({ searchParams, params }: PageProps) { - const resolvedParams = (await params) ?? {}; - const rawLocale = resolvedParams.locale; - const locale = (Array.isArray(rawLocale) ? rawLocale[0] : rawLocale) ?? "zh"; - - const resolvedSearchParams = (await searchParams) ?? {}; + const { locale } = await params; + const resolvedSearchParams = await searchParams; const t = await getTranslations("keywordCloud"); // 如果 URL 中有 quarter 参数,使用它;否则使用默认季度 - const rawQuarter = resolvedSearchParams.quarter; - const quarter = (Array.isArray(rawQuarter) ? rawQuarter[0] : rawQuarter) || "2024-Q1"; - - const texts = { - loading: t("loading"), - loadFailed: t("loadFailed"), - retry: t("retry"), - hotKeyword: t("hotKeyword"), - }; - - return ( -
- {/* 页面标题 */} -
-
- {t("badge")} -
-

- {t("title")}{" "} - - {t("titleHighlight")} - -

-

- {t("subtitle")} -

-
- - {/* 词云组件 */} - -
- ); + const quarter = resolvedSearchParams.quarter || "2024-Q1"; }