fix: 修复 Next.js 15 async params 兼容性问题并更新文档
- 修复关键词词云页面和 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>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -2,12 +2,8 @@ import { KeywordCloud } from './components/KeywordCloud';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
locale: string;
|
||||
};
|
||||
searchParams: {
|
||||
quarter?: string;
|
||||
};
|
||||
params: Promise<{ locale: string }>;
|
||||
searchParams: Promise<{ quarter?: string }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
@@ -26,10 +22,11 @@ export async function generateMetadata({
|
||||
|
||||
export default async function KeywordCloudPage({ searchParams, params }: PageProps) {
|
||||
const { locale } = await params;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const t = await getTranslations('keywordCloud');
|
||||
|
||||
// 如果 URL 中有 quarter 参数,使用它;否则使用默认季度
|
||||
const quarter = searchParams.quarter || '2024-Q1';
|
||||
const quarter = resolvedSearchParams.quarter || '2024-Q1';
|
||||
|
||||
const texts = {
|
||||
loading: t('loading'),
|
||||
|
||||
@@ -9,10 +9,10 @@ export const dynamic = 'force-dynamic';
|
||||
*/
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { quarter: string } }
|
||||
{ params }: { params: Promise<{ quarter: string }> }
|
||||
) {
|
||||
try {
|
||||
const { quarter } = params;
|
||||
const { quarter } = await params;
|
||||
|
||||
// 验证 quarter 格式
|
||||
if (!/^\d{4}-Q[1-4]$/.test(quarter)) {
|
||||
|
||||
Reference in New Issue
Block a user