autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
201 lines (200 loc) • 8.99 kB
JavaScript
/**
* Next.js 15 Enhancement Pack
* 条件: { languages: ['typescript', 'javascript'], frameworks: ['nextjs'] }
*
* 覆盖 Next.js 15 App Router 生态:
* - App Router 文件约定 (layout/page/loading/error/not-found/route)
* - Server Actions ("use server")
* - Metadata API (generateMetadata / export metadata)
* - Middleware (middleware.ts)
* - RSC 数据获取模式 (async Server Components / fetch cache)
* - Parallel Routes / Intercepting Routes
*/
import { EnhancementPack } from './EnhancementPack.js';
class NextjsEnhancement extends EnhancementPack {
get id() {
return 'nextjs';
}
get displayName() {
return 'Next.js 15 App Router Enhancement';
}
get conditions() {
return { languages: ['typescript', 'javascript'], frameworks: ['nextjs'] };
}
getExtraDimensions() {
return [
{
id: 'nextjs-app-router-scan',
label: 'App Router 结构分析',
guide: 'Next.js App Router 文件约定分析 — layout.tsx/page.tsx/loading.tsx/error.tsx/not-found.tsx 分布、Route Groups、Parallel Routes (@folder)、Intercepting Routes (..)、generateStaticParams 使用',
tierHint: 2,
knowledgeTypes: ['architecture'],
skillWorthy: true,
dualOutput: true,
skillMeta: {
name: 'project-nextjs-app-router',
description: 'Next.js App Router file conventions, route groups and parallel routes (auto-generated by enhancement)',
},
},
{
id: 'nextjs-server-actions-scan',
label: 'Server Actions 分析',
guide: 'Server Actions 分析 — "use server" 函数分布、表单绑定 (action: any={})、useFormState/useFormStatus 配合、revalidatePath/revalidateTag 缓存策略',
tierHint: 2,
knowledgeTypes: ['architecture', 'code-pattern'],
skillWorthy: true,
dualOutput: true,
skillMeta: {
name: 'project-nextjs-server-actions',
description: 'Next.js Server Actions, form bindings and revalidation patterns (auto-generated by enhancement)',
},
},
{
id: 'nextjs-data-fetching-scan',
label: '数据获取模式分析',
guide: 'Next.js 数据获取模式 — async Server Component 直接 fetch/DB 查询、fetch() cache/revalidate 选项、generateStaticParams、Streaming + Suspense 加载策略',
tierHint: 2,
knowledgeTypes: ['code-pattern'],
skillWorthy: true,
dualOutput: true,
skillMeta: {
name: 'project-nextjs-data-fetching',
description: 'Next.js data fetching — Server Component queries, fetch caching and streaming (auto-generated by enhancement)',
},
},
];
}
getGuardRules() {
return [
{
ruleId: 'nextjs-no-client-in-server-action',
category: 'correctness',
dimension: 'file',
severity: 'error',
languages: ['typescript', 'javascript'],
pattern: /['"]use server['"]\s*;?\s*\n[\s\S]*?(?:useState|useEffect|useRef|useContext)\s*\(/,
message: '"use server" 文件/函数中不能使用 React Hooks — Server Actions 运行在服务端',
},
{
ruleId: 'nextjs-use-server-top-level',
category: 'correctness',
dimension: 'file',
severity: 'warning',
languages: ['typescript', 'javascript'],
pattern: /(?:function|const)\s+\w+[\s\S]*?\{[\s\S]*?['"]use server['"]/,
message: '"use server" 指令应放在文件顶部(标记整个文件为 Server Actions)或 async 函数体开头',
},
{
ruleId: 'nextjs-no-window-in-server',
category: 'correctness',
dimension: 'file',
severity: 'error',
languages: ['typescript', 'javascript'],
pattern: /(?:^(?!.*['"]use client['"])[\s\S]*?)\b(?:window|document|localStorage|sessionStorage)\b/,
message: 'Server Component 中不能访问 window/document 等浏览器 API — 添加 "use client" 或检查运行环境',
},
{
ruleId: 'nextjs-metadata-with-use-client',
category: 'correctness',
dimension: 'file',
severity: 'error',
languages: ['typescript', 'javascript'],
pattern: /['"]use client['"][\s\S]*?export\s+(?:const\s+metadata|async\s+function\s+generateMetadata)/,
message: 'metadata / generateMetadata 只能在 Server Component 中导出 — 不能与 "use client" 共存',
},
{
ruleId: 'nextjs-fetch-no-store',
category: 'performance',
dimension: 'file',
severity: 'info',
languages: ['typescript', 'javascript'],
pattern: /fetch\s*\([^)]+\)\s*(?![\s\S]*?(?:cache|revalidate|next))/,
message: 'Next.js 15 默认不缓存 fetch,明确指定 { cache: "force-cache" } 或 { next: { revalidate: N } } 以启用缓存',
},
];
}
detectPatterns(astSummary) {
const patterns = [];
// ── App Router file conventions ──
for (const m of astSummary.methods || []) {
if (!m.className && m.isExported) {
// Default export in page/layout files
if (m.name === 'default' || /^(?:Page|Layout|Loading|Error|NotFound)$/.test(m.name)) {
patterns.push({
type: 'nextjs-app-router-component',
methodName: m.name,
line: m.line,
confidence: 0.8,
});
}
// generateMetadata / generateStaticParams
if (m.name === 'generateMetadata' || m.name === 'generateStaticParams') {
patterns.push({
type: 'nextjs-metadata-generator',
methodName: m.name,
line: m.line,
confidence: 0.95,
});
}
}
}
// ── Server Actions (async functions with "use server") ──
for (const m of astSummary.methods || []) {
if (m.isAsync && !m.className) {
const nameLower = m.name?.toLowerCase() || '';
if (nameLower.startsWith('create') ||
nameLower.startsWith('update') ||
nameLower.startsWith('delete') ||
nameLower.startsWith('submit') ||
nameLower.includes('action')) {
patterns.push({
type: 'nextjs-server-action',
methodName: m.name,
line: m.line,
confidence: 0.65,
});
}
}
}
// ── Route Handlers (GET/POST/PUT/DELETE/PATCH exports) ──
for (const m of astSummary.methods || []) {
if (m.isExported &&
!m.className &&
/^(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/.test(m.name)) {
patterns.push({
type: 'nextjs-route-handler',
methodName: m.name,
line: m.line,
confidence: 0.95,
});
}
}
// ── Middleware ──
for (const m of astSummary.methods || []) {
if (m.isExported && m.name === 'middleware') {
patterns.push({
type: 'nextjs-middleware',
methodName: m.name,
line: m.line,
confidence: 0.9,
});
}
}
// ── Next.js specific imports ──
const nextImports = (astSummary.imports || []).filter((imp) => imp.includes('next/') ||
imp.includes('next/server') ||
imp.includes('next/navigation') ||
imp.includes('next/image') ||
imp.includes('next/link') ||
imp.includes('next/font'));
if (nextImports.length > 0) {
patterns.push({
type: 'nextjs-framework-usage',
importCount: nextImports.length,
confidence: 0.9,
});
}
return patterns;
}
}
export const pack = new NextjsEnhancement();