关于 nextjs-app-architecture
面向构建或审查 Next.js 16+ App Router 应用的开发者,指导采用按功能切分的 RSC 架构。工作流覆盖数据查询、组件、页面边界和交互,并附带逐项核对清单。
主要功能
- 01按功能组织代码与数据查询
- 02保持页面同步并组合内容
- 03以 Suspense 管理加载边界
- 04区分服务器组件与客户端组件
- 05提供架构审查核对清单
适用场景
- 01从头搭建 Next.js 应用架构
- 02审查或重构现有 App Router 项目
- 03规划数据查询和服务端操作位置
- 04检查页面加载与交互实现
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ aurorascharff/ nextjs-app-architecture-skill --skill nextjs-app-architecture安装后在 Claude Code 中输入/nextjs-app-architecture确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSEmetadata.jsonREADME.mdreferences/feature-folders.mdexample.mdpages-suspense.mdinstant-navigation.mdqueries-actions.mdcache-components.mdcomponents.mdsingle-page-applications.mdtesting.mdux-patterns.md详细介绍
Next.js App 架构
用于构建和审查 Next.js 16+ App Router 应用的工作流,确保它们遵循统一的、按功能切片的 RSC 架构,类似于 next-beats。
请逐步遵循以下工作流——这样可以从设计上确保不变量。对于每项决策,请加载对应步骤中指定的参考资料。框架的 机制(API 签名、配置选项、钩子约定)请参阅链接文档,不要复述或自行编造。仅限 canary 使用的 Next.js API 链接指向 preview.nextjs.org。
前置条件
在更改 Next.js 应用之前,请确保项目已配置好供 AI Agent 阅读与版本匹配的文档。请遵循 AI 编码 Agent 指南:优先使用项目的 AGENTS.md / 随附文档,并在缺失时创建或更新它们。然后使用此技能进行架构决策。
架构目标
构建用于描述加载体验的页面,而不是充当路由加载器的页面:
app/**/page.tsx和layout.tsx是同步组合层:用于静态界面、分区标题、<Suspense>边界、错误边界和过渡包装器。- 功能组件在服务器端负责读取数据。它们接收最少且稳定的输入(
id、slug、handle、已解析的筛选值)或已获取的记录,绝不接收原始的params/searchParams。 - 查询和操作放在功能文件夹中。组件导入查询;客户端叶子组件直接导入操作。
- 当服务端标签和客户端查询键描述同一功能数据时,由纯功能本地缓存约定统一管理这些标识。
- 稳定的界面、包装器和骨架屏应保持布局不变:卡片/面板放在 Suspense 边界之外,回退内容仅替换依赖数据的主体。
不变量(每项更改都必须满足)
不可妥协的要求。工作流负责生成这些要求;最终检查负责验证。
- 页面负责组合,绝不负责获取数据。 页面/布局导入功能组件并放置
<Suspense>。不得内联查询或领域逻辑。仅当需要围绕connection()、redirect()或已解析的路由属性放置边界时,才允许使用简短的路由局部控制流辅助函数。 - 页面保持同步。 使用
params.then()/searchParams.then()或Promise.all([params, searchParams]).then(...),绝不在顶层使用await params——这样页面框架就能渲染到静态外壳中,只有依赖数据的部分会挂起。 - 功能组件接收 ID,而非路由属性。 在页面边界处解析
params/searchParams,并将普通值(id、slug、query)传给功能组件。 - 默认使用异步服务器组件。 仅在需要 hooks、事件处理程序或浏览器 API 时使用
'use client',且只用于叶节点,绝不用于服务器内容的父组件。 - Suspense 边界由页面负责,骨架屏由功能组件负责。 功能组件绝不自行预先包装
<Suspense>;稳定的包装器/卡片/页面框架应包裹边界,而不是在后备内容和最终内容中重复出现。 - **骨架屏与组件定义在同一文件中,**并与组件一同导出,定义在文件末尾。
Feed和FeedSkeleton是同级组件。 - 查询放在
<domain>-queries.ts中(import 'server-only');操作放在<domain>-actions.ts中('use server')。文件名与文件夹名一致,即使是子概念也如此。 - 功能文件夹按产品归属划分。 由实体拥有的子概念(favorite、like、vote、bookmark)归入其父级。组合多个领域的路由级体验可在单独的功能中拥有自己的 UI 和状态,而每个领域仍保留自己的查询和操作。
- 客户端组件直接导入操作——绝不只为调用服务器操作而将其作为属性传入。
- 功能局部的缓存协调由所属领域负责。 将纯标签/键放在
<domain>-cache.ts中,将客户端查询定义放在<domain>-query-options.ts中,将 hook 包装器放在hooks/use-*.ts中,将简短的客户端叶节点放在components/中;只有在实际出现跨功能复用后,才提升支持代码。 - 交互式异步 UI 应将服务器数据留在服务器端,并将客户端状态限制在交互局部。 使用
useOptimistic、useTransition、reducers、URL/搜索参数和表单操作,不要使用镜像属性状态、派生状态 effect 或手动实现的待处理数组。
工作流
对于从零构建、功能开发或审计,请按顺序执行以下步骤。每一步都注明要参考的文档以及必须通过的检查。
- 选择模式。
- 从零构建: 编写代码前,先规划路由、真实的领域名词、静态外壳和预期的加载分组。
- 审计/重构: 先检查现有的
app/页面;列出所有异步页面、页面级查询导入、路由属性泄漏、缺失的 Suspense 边界,以及功能文件夹不匹配项。 →references/example.md了解目标结构;references/feature-folders.md了解文件放置方式。 ✓ 明确你是在创建架构,还是在将基于加载器的代码转换为该架构。
- 确定工作放置位置。 动手前先确定功能文件夹。
→
references/feature-folders.md(决策树 + 合并规则)。 ✓ 放入真实领域、跨领域的产品体验,或归入合适的父级。 - 编写查询;如果客户端缓存会共享其数据,还要编写缓存契约。 将服务端读取放入
<domain>-queries.ts,并添加import 'server-only';将共享的标签/键标识放入纯净的<domain>-cache.ts中。 →references/queries-actions.md;使用 SWR/TanStack Query 时 →references/single-page-applications.md;使用cacheComponents: true时,还要参考 →references/cache-components.md。 ✓ 缓存标识只定义一次;服务端读取仅在服务端运行,在 Cache Components 下进行缓存、标记和生命周期管理,并返回领域类型,而非 ORM 行。 - 编写操作(如果涉及变更)。放入
features/<domain>/<domain>-actions.ts,并在文件顶部添加'use server'。 →references/queries-actions.md。 ✓ 重新检查身份验证、验证输入、在 Cache Components 下使匹配的缓存标签失效(仅在有充分理由的动态读取中使用refresh()),并返回判别联合类型。 - 构建组件和骨架。
features/<domain>/components/<name>.tsx:异步服务端组件,从最少的属性中获取所需数据;仅在交互式叶节点上使用'use client'。 →references/components.md;使用客户端数据库或严格的 SPA/CSR 功能时 →references/single-page-applications.md。 ✓ 组件接收 ID/句柄/已解析的筛选条件或已解析的记录,而不是params;骨架作为同级导出放在文件末尾;不使用别名骨架包装器。 - 组合页面。
app/<route>/page.tsx:保持同步,使用params.then()/Promise.all(...).then(...),在数据主体周围放置 Suspense,并用错误边界包裹可能独立失败的部分。 →references/pages-suspense.md;使用cacheComponents: true时,还要参考references/instant-navigation.md,了解哪些链接会预取,以及哪些读取会等待导航完成。 ✓ 路由属性转换为普通值;稳定的卡片/区块在用于设置布局时包裹 Suspense;页面上清晰可见各边界。 - 添加交互(如有):乐观更新、待处理状态、Toast、确认。
→
references/ux-patterns.md。 ✓ 避免重复反馈;乐观更新 reducer/操作与功能放在一起;可分享状态由 URL/搜索参数管理;客户端 effect 用于同步外部系统,而非派生 React 状态。 - 验证:在宣布完成前,对照下面的检查清单进行核查。如果更改涉及加载流程,请通过端到端测试确保其稳定。
→
references/testing.md。 ✓ 测试使用感知可见性的定位器,断言未缓存工作完成前哪些内容可用。
完成前验证
对照每项不变量检查差异 — 阅读已更改的文件即可逐项核验:
- 没有页面/布局导入
*-queries文件或在行内定义可复用的领域 UI;任何行内辅助逻辑都只能用于路由本地的控制流。 - 每个带参数的页面都是同步的,并使用
params.then()/searchParams.then()/Promise.all([params, searchParams]).then(...)。 - 功能组件接收普通 ID/句柄/已解析的筛选条件或已解析的记录;没有功能属性命名为
params或searchParams。 - 用于页面数据的每个
<Suspense>都位于页面中;功能组件不会预先为自身包裹 Suspense。 - 稳定的包装层/卡片/界面框架位于 Suspense 之外;回退内容和最终内容不会重复相同的外层卡片。
- 每个组件都在同一文件末尾定义了其实际的
*Skeleton;不会仅为满足属性要求而创建简易骨架屏别名。 - 骨架屏和内容的高度相同(已测量),分区标题位于边界之外,可为空的分区会预留空间。
- 可变高度分区下方的分区是嵌套在该分区中的同级边界,不会被该分区等待,也不会作为子项传入该分区。
-
unstable_prefetch()/unstable_navigation()在组件内、查询调用前已等待;没有仅为承载门控而创建的包装查询。 - 每个
*-queries.ts都以import 'server-only'开头;每个*-actions.ts都以'use server'开头。 - 启用
cacheComponents: true时,可复用读取使用'use cache'/cacheTag/cacheLife,或在适当情况下使用'use cache: private'/'use cache: remote';任何动态读取都有意为之且有充分理由。 - 个性化共享缓存作用域从未缓存的功能包装层接收可信且已规范化的身份值;它不会读取请求 API,也不会在缓存作用域内信任客户端提供的身份。
- 如果某项读取的每次写入都会调用
updateTag(),则使用cacheLife('max');只有数据会在应用操作之外更改时,才使用更短的配置。 - 触及缓存读取的变更会针对相应标签调用
updateTag()/revalidateTag(..., 'max');refresh()不能替代标签失效操作。 -
prefetch={true}仅用于用户可预见会访问的链接,或用于每个链接的预取已通过将繁重部分置于unstable_navigation()之后而变得廉价的行。 - 操作文件命名为
<folder>-actions.ts;没有实体所属的子概念单独创建文件夹,跨领域产品功能也不会接管实体查询/操作。 - 同时使用服务端标签和客户端查询键的功能,会在纯
<domain>-cache.ts中统一定义它们;查询、操作、数据水合、查询选项和钩子都从该文件导入。 - 功能本地的客户端支持文件放在最合适且最精简的位置:查询选项放在功能根目录,
use-*钩子包装器放在hooks/,叶组件放在components/,只有真正跨功能复用时才放置共享支持代码。 -
'use client'组件都是叶组件 — 它们导入操作/钩子/提供程序,而非异步服务端组件。需要服务端数据的客户端组件会在功能中有对应的服务端部分(ComposePanel→ComposePanelClient);页面或布局不会为其创建查询 Promise。 - 客户端叶组件使用
useOptimistic、过渡、reducer、URL 状态或表单操作来处理交互;不会在 effect 中对派生的 React 状态调用setState。 - 操作可能拒绝输入的表单会保留用户输入的内容:操作状态会返回已提交的值,字段会将其作为
defaultValue读取。 - 变更会验证输入并使受影响的数据失效。
参考索引
references/feature-folders.md— 代码放在哪里:文件夹布局、缓存约定、命名和子概念合并。references/queries-actions.md— 查询/操作规则:仅限服务端、去重、验证、失效处理、返回结构。references/components.md— 服务端/客户端边界、骨架屏、use()、单次使用辅助函数、实时数据。references/pages-suspense.md— 页面组合、params.then()、Suspense 的放置位置、CLS、错误边界。references/instant-navigation.md— 预取决策:prefetch={true}、三个渲染阶段及其门控、验证导航显示的内容、隐藏路由。references/cache-components.md—cacheComponents决策:缓存哪些读取、使用哪种指令、如何使缓存失效。references/single-page-applications.md— 客户端缓存决策:放置位置、服务端预置数据、Cache Components 协调、水合和变更。references/ux-patterns.md— 交互决策:乐观更新与待处理状态和内联错误的取舍、Toast、action-prop、确认操作。references/testing.md— 端到端测试:使用instant()断言加载顺序、跨隐藏路由定位内容、测试变更。references/example.md— next-beats 参考应用:不变量 → 文件映射,用于查看任何规则在真实代码中的应用。
评论
支持 Markdown