@mison/ag-kit-cn 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +285 -0
- package/.agent/agents/backend-specialist.md +268 -0
- package/.agent/agents/code-archaeologist.md +106 -0
- package/.agent/agents/database-architect.md +225 -0
- package/.agent/agents/debugger.md +225 -0
- package/.agent/agents/devops-engineer.md +242 -0
- package/.agent/agents/documentation-writer.md +104 -0
- package/.agent/agents/explorer-agent.md +73 -0
- package/.agent/agents/frontend-specialist.md +618 -0
- package/.agent/agents/game-developer.md +162 -0
- package/.agent/agents/mobile-developer.md +382 -0
- package/.agent/agents/orchestrator.md +438 -0
- package/.agent/agents/penetration-tester.md +188 -0
- package/.agent/agents/performance-optimizer.md +187 -0
- package/.agent/agents/product-manager.md +112 -0
- package/.agent/agents/product-owner.md +95 -0
- package/.agent/agents/project-planner.md +405 -0
- package/.agent/agents/qa-automation-engineer.md +103 -0
- package/.agent/agents/security-auditor.md +170 -0
- package/.agent/agents/seo-specialist.md +111 -0
- package/.agent/agents/test-engineer.md +158 -0
- package/.agent/mcp_config.json +12 -0
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +84 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +74 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +57 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/bash-linux/SKILL.md +201 -0
- package/.agent/skills/behavioral-modes/SKILL.md +264 -0
- package/.agent/skills/brainstorming/SKILL.md +164 -0
- package/.agent/skills/brainstorming/dynamic-questioning.md +359 -0
- package/.agent/skills/clean-code/SKILL.md +200 -0
- package/.agent/skills/code-review-checklist/SKILL.md +125 -0
- package/.agent/skills/database-design/SKILL.md +54 -0
- package/.agent/skills/database-design/database-selection.md +43 -0
- package/.agent/skills/database-design/indexing.md +39 -0
- package/.agent/skills/database-design/migrations.md +50 -0
- package/.agent/skills/database-design/optimization.md +36 -0
- package/.agent/skills/database-design/orm-selection.md +30 -0
- package/.agent/skills/database-design/schema-design.md +56 -0
- package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/documentation-templates/SKILL.md +194 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +307 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +727 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1118 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agent/skills/game-development/SKILL.md +167 -0
- package/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +155 -0
- package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/intelligent-routing/SKILL.md +335 -0
- package/.agent/skills/lint-and-validate/SKILL.md +44 -0
- package/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -0
- package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/mobile-design/SKILL.md +394 -0
- package/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/.agent/skills/mobile-design/mobile-design-thinking.md +355 -0
- package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/.agent/skills/mobile-design/mobile-performance.md +767 -0
- package/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/.agent/skills/mobile-design/mobile-typography.md +432 -0
- package/.agent/skills/mobile-design/platform-android.md +666 -0
- package/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +311 -0
- package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +241 -0
- package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +489 -0
- package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +263 -0
- package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
- package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +431 -0
- package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +683 -0
- package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +149 -0
- package/.agent/skills/nextjs-react-expert/SKILL.md +286 -0
- package/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
- package/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
- package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/.agent/skills/parallel-agents/SKILL.md +194 -0
- package/.agent/skills/performance-profiling/SKILL.md +149 -0
- package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/.agent/skills/plan-writing/SKILL.md +152 -0
- package/.agent/skills/powershell-windows/SKILL.md +166 -0
- package/.agent/skills/python-patterns/SKILL.md +441 -0
- package/.agent/skills/red-team-tactics/SKILL.md +203 -0
- package/.agent/skills/rust-pro/SKILL.md +190 -0
- package/.agent/skills/seo-fundamentals/SKILL.md +135 -0
- package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +215 -0
- package/.agent/skills/server-management/SKILL.md +161 -0
- package/.agent/skills/systematic-debugging/SKILL.md +114 -0
- package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
- package/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.agent/skills/vulnerability-scanner/checklists.md +131 -0
- package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +459 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +242 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +80 -0
- package/.agent/workflows/restore-localize-compat.md +525 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +295 -0
- package/AGENT_FLOW.md +609 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/bin/adapters/base.js +63 -0
- package/bin/adapters/codex.js +391 -0
- package/bin/adapters/gemini.js +137 -0
- package/bin/ag-kit.js +1336 -0
- package/bin/core/builder.js +80 -0
- package/bin/core/generator.js +59 -0
- package/bin/core/resource-loader.js +64 -0
- package/bin/core/transformer.js +208 -0
- package/bin/interactive.js +65 -0
- package/bin/utils/atomic-writer.js +97 -0
- package/bin/utils/git-helper.js +68 -0
- package/bin/utils/managed-block.js +65 -0
- package/bin/utils/manifest.js +241 -0
- package/bin/utils.js +82 -0
- package/docs/codex-rules-template.md +36 -0
- package/docs/mapping-spec.md +68 -0
- package/docs/multi-target-adapter.md +80 -0
- package/docs/official/README.md +53 -0
- package/docs/official/antigravity/agent-modes-settings.md +64 -0
- package/docs/official/antigravity/rules-workflows.md +96 -0
- package/docs/official/antigravity/skills.md +147 -0
- package/docs/official/codex/agents-md.md +119 -0
- package/docs/official/codex/config-advanced.md +358 -0
- package/docs/official/codex/config-basic.md +141 -0
- package/docs/official/codex/config-reference.md +223 -0
- package/docs/official/codex/config-sample.md +216 -0
- package/docs/official/codex/mcp.md +107 -0
- package/docs/official/codex/rules.md +79 -0
- package/docs/official/codex/skills.md +114 -0
- package/docs/official/sources-index.md +32 -0
- package/docs/operations.md +145 -0
- package/docs/terminology-style-guide.md +69 -0
- package/package.json +51 -0
- package/scripts/postinstall-check.js +112 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
# 3. 服务端性能 (Server-Side Performance)
|
|
2
|
+
|
|
3
|
+
> **影响:** 高 (HIGH)
|
|
4
|
+
> **重点:** 通过优化服务端渲染与数据获取,消除服务端瀑布流并降低响应时间。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 概览
|
|
9
|
+
|
|
10
|
+
本节包含 **7 条规则**,聚焦服务端性能。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 规则 3.1:像 API 路由一样认证 Server Actions
|
|
15
|
+
|
|
16
|
+
**影响:** 严重 (CRITICAL)
|
|
17
|
+
**标签:** server, server-actions, authentication, security, authorization
|
|
18
|
+
|
|
19
|
+
## 像 API 路由一样认证 Server Actions
|
|
20
|
+
|
|
21
|
+
**影响:严重 (防止未授权访问服务端变更操作)**
|
|
22
|
+
|
|
23
|
+
Server Actions(带有 `"use server"` 的函数)和 API 路由一样,都是对外公开端点。必须在每个 Server Action **内部**做认证与鉴权。不要只依赖中间件、布局守卫或页面级检查,因为 Server Actions 可以被直接调用。
|
|
24
|
+
|
|
25
|
+
Next.js 文档明确指出:“要将 Server Actions 按公开 API 端点同等安全级别对待,并验证用户是否有权限执行该变更。”
|
|
26
|
+
|
|
27
|
+
**错误示例(未做认证检查):**
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
'use server'
|
|
31
|
+
|
|
32
|
+
export async function deleteUser(userId: string) {
|
|
33
|
+
// Anyone can call this! No auth check
|
|
34
|
+
await db.user.delete({ where: { id: userId } })
|
|
35
|
+
return { success: true }
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**正确示例(在 Action 内部做认证):**
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
'use server'
|
|
43
|
+
|
|
44
|
+
import { verifySession } from '@/lib/auth'
|
|
45
|
+
import { unauthorized } from '@/lib/errors'
|
|
46
|
+
|
|
47
|
+
export async function deleteUser(userId: string) {
|
|
48
|
+
// Always check auth inside the action
|
|
49
|
+
const session = await verifySession()
|
|
50
|
+
|
|
51
|
+
if (!session) {
|
|
52
|
+
throw unauthorized('Must be logged in')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check authorization too
|
|
56
|
+
if (session.user.role !== 'admin' && session.user.id !== userId) {
|
|
57
|
+
throw unauthorized('Cannot delete other users')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await db.user.delete({ where: { id: userId } })
|
|
61
|
+
return { success: true }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**结合输入校验:**
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
'use server'
|
|
69
|
+
|
|
70
|
+
import { verifySession } from '@/lib/auth'
|
|
71
|
+
import { z } from 'zod'
|
|
72
|
+
|
|
73
|
+
const updateProfileSchema = z.object({
|
|
74
|
+
userId: z.string().uuid(),
|
|
75
|
+
name: z.string().min(1).max(100),
|
|
76
|
+
email: z.string().email()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
export async function updateProfile(data: unknown) {
|
|
80
|
+
// Validate input first
|
|
81
|
+
const validated = updateProfileSchema.parse(data)
|
|
82
|
+
|
|
83
|
+
// Then authenticate
|
|
84
|
+
const session = await verifySession()
|
|
85
|
+
if (!session) {
|
|
86
|
+
throw new Error('Unauthorized')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Then authorize
|
|
90
|
+
if (session.user.id !== validated.userId) {
|
|
91
|
+
throw new Error('Can only update own profile')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Finally perform the mutation
|
|
95
|
+
await db.user.update({
|
|
96
|
+
where: { id: validated.userId },
|
|
97
|
+
data: {
|
|
98
|
+
name: validated.name,
|
|
99
|
+
email: validated.email
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
return { success: true }
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
参考: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 规则 3.2:避免在 RSC Props 中重复序列化
|
|
112
|
+
|
|
113
|
+
**影响:** 低 (LOW)
|
|
114
|
+
**标签:** server, rsc, serialization, props, client-components
|
|
115
|
+
|
|
116
|
+
## 避免在 RSC Props 中重复序列化
|
|
117
|
+
|
|
118
|
+
**影响:低 (通过避免重复序列化来减少网络负载)**
|
|
119
|
+
|
|
120
|
+
RSC 到客户端的序列化是按对象引用去重,不是按值去重。相同引用只会序列化一次;新引用会再次序列化。像 `.toSorted()`、`.filter()`、`.map()` 这类转换应尽量放在客户端,而不是服务端。
|
|
121
|
+
|
|
122
|
+
**错误示例(数组被重复序列化):**
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
// RSC: sends 6 strings (2 arrays × 3 items)
|
|
126
|
+
<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**正确示例(只发送 3 个字符串):**
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
// RSC: send once
|
|
133
|
+
<ClientList usernames={usernames} />
|
|
134
|
+
|
|
135
|
+
// Client: transform there
|
|
136
|
+
'use client'
|
|
137
|
+
const sorted = useMemo(() => [...usernames].sort(), [usernames])
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**嵌套去重行为:**
|
|
141
|
+
|
|
142
|
+
去重会递归生效,不同数据类型的收益不同:
|
|
143
|
+
|
|
144
|
+
- `string[]`、`number[]`、`boolean[]`:**高影响**,数组和其中的原始值都会完整重复
|
|
145
|
+
- `object[]`:**低影响**,数组结构会重复,但内部对象会按引用去重
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
// string[] - duplicates everything
|
|
149
|
+
usernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings
|
|
150
|
+
|
|
151
|
+
// object[] - duplicates array structure only
|
|
152
|
+
users={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**会破坏去重的操作(会创建新引用):**
|
|
156
|
+
|
|
157
|
+
- 数组:`.toSorted()`、`.filter()`、`.map()`、`.slice()`、`[...arr]`
|
|
158
|
+
- 对象:`{...obj}`、`Object.assign()`、`structuredClone()`、`JSON.parse(JSON.stringify())`
|
|
159
|
+
|
|
160
|
+
**更多示例:**
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// ❌ Bad
|
|
164
|
+
<C users={users} active={users.filter(u => u.active)} />
|
|
165
|
+
<C product={product} productName={product.name} />
|
|
166
|
+
|
|
167
|
+
// ✅ Good
|
|
168
|
+
<C users={users} />
|
|
169
|
+
<C product={product} />
|
|
170
|
+
// Do filtering/destructuring in client
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**例外:** 如果转换计算成本较高,或客户端不需要原始数据,可直接传递派生数据。
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 规则 3.3:跨请求 LRU 缓存
|
|
178
|
+
|
|
179
|
+
**影响:** 高 (HIGH)
|
|
180
|
+
**标签:** server, cache, lru, cross-request
|
|
181
|
+
|
|
182
|
+
## 跨请求 LRU 缓存
|
|
183
|
+
|
|
184
|
+
`React.cache()` 仅在单次请求内生效。对于跨连续请求共享的数据(例如用户先点按钮 A 再点按钮 B),应使用 LRU 缓存。
|
|
185
|
+
|
|
186
|
+
**实现:**
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { LRUCache } from 'lru-cache'
|
|
190
|
+
|
|
191
|
+
const cache = new LRUCache<string, any>({
|
|
192
|
+
max: 1000,
|
|
193
|
+
ttl: 5 * 60 * 1000 // 5 minutes
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
export async function getUser(id: string) {
|
|
197
|
+
const cached = cache.get(id)
|
|
198
|
+
if (cached) return cached
|
|
199
|
+
|
|
200
|
+
const user = await db.user.findUnique({ where: { id } })
|
|
201
|
+
cache.set(id, user)
|
|
202
|
+
return user
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Request 1: DB query, result cached
|
|
206
|
+
// Request 2: cache hit, no DB query
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
当用户在几秒内触发的连续操作会命中多个、且需要相同数据的端点时,优先使用这一模式。
|
|
210
|
+
|
|
211
|
+
**结合 Vercel 的 [Fluid Compute](https://vercel.com/docs/fluid-compute):** 多个并发请求可共享同一函数实例与缓存,LRU 缓存会更有效,不一定需要 Redis 这类外部存储。
|
|
212
|
+
|
|
213
|
+
**传统 serverless 场景:** 每次调用相互隔离,跨进程缓存通常需要 Redis。
|
|
214
|
+
|
|
215
|
+
参考: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 规则 3.4:最小化 RSC 边界的序列化开销
|
|
220
|
+
|
|
221
|
+
**影响:** 高 (HIGH)
|
|
222
|
+
**标签:** server, rsc, serialization, props
|
|
223
|
+
|
|
224
|
+
## 最小化 RSC 边界的序列化开销
|
|
225
|
+
|
|
226
|
+
React 的服务端/客户端边界会把对象属性序列化为字符串,并注入到 HTML 响应与后续 RSC 请求中。该数据会直接影响页面体积与加载时间,所以 **体积非常关键**。只传客户端真正需要的字段。
|
|
227
|
+
|
|
228
|
+
**错误示例(序列化了全部 50 个字段):**
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
async function Page() {
|
|
232
|
+
const user = await fetchUser() // 50 fields
|
|
233
|
+
return <Profile user={user} />
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
'use client'
|
|
237
|
+
function Profile({ user }: { user: User }) {
|
|
238
|
+
return <div>{user.name}</div> // uses 1 field
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**正确示例(只序列化 1 个字段):**
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
async function Page() {
|
|
246
|
+
const user = await fetchUser()
|
|
247
|
+
return <Profile name={user.name} />
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
'use client'
|
|
251
|
+
function Profile({ name }: { name: string }) {
|
|
252
|
+
return <div>{name}</div>
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 规则 3.5:通过组件组合并行获取数据
|
|
259
|
+
|
|
260
|
+
**影响:** 严重 (CRITICAL)
|
|
261
|
+
**标签:** server, rsc, parallel-fetching, composition
|
|
262
|
+
|
|
263
|
+
## 通过组件组合并行获取数据
|
|
264
|
+
|
|
265
|
+
React Server Components 在组件树内默认按顺序执行。通过重新组织组件结构,可以让数据获取并行进行。
|
|
266
|
+
|
|
267
|
+
**错误示例(Sidebar 需等待 Page 的 fetch 完成):**
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
export default async function Page() {
|
|
271
|
+
const header = await fetchHeader()
|
|
272
|
+
return (
|
|
273
|
+
<div>
|
|
274
|
+
<div>{header}</div>
|
|
275
|
+
<Sidebar />
|
|
276
|
+
</div>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function Sidebar() {
|
|
281
|
+
const items = await fetchSidebarItems()
|
|
282
|
+
return <nav>{items.map(renderItem)}</nav>
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**正确示例(两个 fetch 同时执行):**
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
async function Header() {
|
|
290
|
+
const data = await fetchHeader()
|
|
291
|
+
return <div>{data}</div>
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function Sidebar() {
|
|
295
|
+
const items = await fetchSidebarItems()
|
|
296
|
+
return <nav>{items.map(renderItem)}</nav>
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export default function Page() {
|
|
300
|
+
return (
|
|
301
|
+
<div>
|
|
302
|
+
<Header />
|
|
303
|
+
<Sidebar />
|
|
304
|
+
</div>
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**使用 `children` 的另一种写法:**
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
async function Header() {
|
|
313
|
+
const data = await fetchHeader()
|
|
314
|
+
return <div>{data}</div>
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function Sidebar() {
|
|
318
|
+
const items = await fetchSidebarItems()
|
|
319
|
+
return <nav>{items.map(renderItem)}</nav>
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function Layout({ children }: { children: ReactNode }) {
|
|
323
|
+
return (
|
|
324
|
+
<div>
|
|
325
|
+
<Header />
|
|
326
|
+
{children}
|
|
327
|
+
</div>
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export default function Page() {
|
|
332
|
+
return (
|
|
333
|
+
<Layout>
|
|
334
|
+
<Sidebar />
|
|
335
|
+
</Layout>
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 规则 3.6:使用 React.cache() 做单请求去重
|
|
343
|
+
|
|
344
|
+
**影响:** 中 (MEDIUM)
|
|
345
|
+
**标签:** server, cache, react-cache, deduplication
|
|
346
|
+
|
|
347
|
+
## 使用 React.cache() 做单请求去重
|
|
348
|
+
|
|
349
|
+
使用 `React.cache()` 对服务端请求去重。认证检查和数据库查询通常收益最大。
|
|
350
|
+
|
|
351
|
+
**用法:**
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { cache } from 'react'
|
|
355
|
+
|
|
356
|
+
export const getCurrentUser = cache(async () => {
|
|
357
|
+
const session = await auth()
|
|
358
|
+
if (!session?.user?.id) return null
|
|
359
|
+
return await db.user.findUnique({
|
|
360
|
+
where: { id: session.user.id }
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
在同一次请求内,多次调用 `getCurrentUser()` 只会执行一次查询。
|
|
366
|
+
|
|
367
|
+
**避免以内联对象作为参数:**
|
|
368
|
+
|
|
369
|
+
`React.cache()` 用浅比较(`Object.is`)判断命中。内联对象每次都会创建新引用,导致无法命中缓存。
|
|
370
|
+
|
|
371
|
+
**错误示例(始终缓存未命中):**
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
const getUser = cache(async (params: { uid: number }) => {
|
|
375
|
+
return await db.user.findUnique({ where: { id: params.uid } })
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
// Each call creates new object, never hits cache
|
|
379
|
+
getUser({ uid: 1 })
|
|
380
|
+
getUser({ uid: 1 }) // Cache miss, runs query again
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**正确示例(缓存命中):**
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
const getUser = cache(async (uid: number) => {
|
|
387
|
+
return await db.user.findUnique({ where: { id: uid } })
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
// Primitive args use value equality
|
|
391
|
+
getUser(1)
|
|
392
|
+
getUser(1) // Cache hit, returns cached result
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
如果必须传对象,请复用同一个引用:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
const params = { uid: 1 }
|
|
399
|
+
getUser(params) // Query runs
|
|
400
|
+
getUser(params) // Cache hit (same reference)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Next.js 特别说明:**
|
|
404
|
+
|
|
405
|
+
在 Next.js 中,`fetch` 已扩展了请求级 memoization。同一个请求内,URL 和选项相同的 `fetch` 会自动去重,因此 `fetch` 场景通常不需要 `React.cache()`。但对于其他异步任务,`React.cache()` 仍然很关键:
|
|
406
|
+
|
|
407
|
+
- 数据库查询(Prisma、Drizzle 等)
|
|
408
|
+
- 重计算任务
|
|
409
|
+
- 认证检查
|
|
410
|
+
- 文件系统操作
|
|
411
|
+
- 非 fetch 的异步工作
|
|
412
|
+
|
|
413
|
+
可用 `React.cache()` 在组件树中对这些操作做去重。
|
|
414
|
+
|
|
415
|
+
参考: [React.cache 文档](https://react.dev/reference/react/cache)
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## 规则 3.7:使用 `after()` 执行非阻塞操作
|
|
420
|
+
|
|
421
|
+
**影响:** 中 (MEDIUM)
|
|
422
|
+
**标签:** server, async, logging, analytics, side-effects
|
|
423
|
+
|
|
424
|
+
## 使用 `after()` 执行非阻塞操作
|
|
425
|
+
|
|
426
|
+
使用 Next.js 的 `after()` 安排“响应发送后再执行”的任务。这样日志、埋点和其他副作用就不会阻塞响应返回。
|
|
427
|
+
|
|
428
|
+
**错误示例(阻塞响应):**
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
import { logUserAction } from '@/app/utils'
|
|
432
|
+
|
|
433
|
+
export async function POST(request: Request) {
|
|
434
|
+
// Perform mutation
|
|
435
|
+
await updateDatabase(request)
|
|
436
|
+
|
|
437
|
+
// Logging blocks the response
|
|
438
|
+
const userAgent = request.headers.get('user-agent') || 'unknown'
|
|
439
|
+
await logUserAction({ userAgent })
|
|
440
|
+
|
|
441
|
+
return new Response(JSON.stringify({ status: 'success' }), {
|
|
442
|
+
status: 200,
|
|
443
|
+
headers: { 'Content-Type': 'application/json' }
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**正确示例(非阻塞):**
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
import { after } from 'next/server'
|
|
452
|
+
import { headers, cookies } from 'next/headers'
|
|
453
|
+
import { logUserAction } from '@/app/utils'
|
|
454
|
+
|
|
455
|
+
export async function POST(request: Request) {
|
|
456
|
+
// Perform mutation
|
|
457
|
+
await updateDatabase(request)
|
|
458
|
+
|
|
459
|
+
// Log after response is sent
|
|
460
|
+
after(async () => {
|
|
461
|
+
const userAgent = (await headers()).get('user-agent') || 'unknown'
|
|
462
|
+
const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
|
|
463
|
+
|
|
464
|
+
logUserAction({ sessionCookie, userAgent })
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
return new Response(JSON.stringify({ status: 'success' }), {
|
|
468
|
+
status: 200,
|
|
469
|
+
headers: { 'Content-Type': 'application/json' }
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
响应会先立即返回,日志在后台异步执行。
|
|
475
|
+
|
|
476
|
+
**常见场景:**
|
|
477
|
+
|
|
478
|
+
- 分析埋点
|
|
479
|
+
- 审计日志
|
|
480
|
+
- 发送通知
|
|
481
|
+
- 缓存失效
|
|
482
|
+
- 清理任务
|
|
483
|
+
|
|
484
|
+
**重要说明:**
|
|
485
|
+
|
|
486
|
+
- 即使响应失败或重定向,`after()` 也会执行
|
|
487
|
+
- 适用于 Server Actions、Route Handlers、Server Components
|
|
488
|
+
|
|
489
|
+
参考: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)
|