Community编程与开发github.com

Next.js 网页应用编码规范

使用一致的 src 文件夹结构、领域控制器、Zod API 契约、组合组件以及明确的客户端/服务器边界来构建和维护 Next.js 网页应用。适用于项目架构和应用编码,同时尊重用户选择的库和提供商。

Next.js 网页应用编码规范 是什么?

本技能为 AI 编码代理(如 Claude Code、Cursor 和 Codex)提供了一套全面的、opinionated 的约定,用于在 Next.js 网页应用中进行项目架构和应用编码。它强制规定了一个结构化的 `src/` 文件夹布局,包含用于 UI 组件、领域控制器(用于客户端和服务器 API 调用)、状态管理和表单验证的特定目录。该技能默认使用 Zod 进行 API 契约和运行时验证,使用 TanStack Query 进行客户端数据获取,使用 Zustand 进行共享交互状态,并使用 Tailwind CSS 结合 class-variance-authority 进行组件样式设计。它强调组件组合,保持应用页面精简,将路由逻辑与组件渲染分离。该技能支持各种 UI 库(如 shadcn/ui 或 Radix),并且是提供商无关的,允许集成任何身份验证或 API 传输机制。它通过使用 `AbortSignal` 实现优雅的请求取消、跨标签页同步以及明确的错误处理模式,涵盖了从路由处理到数据获取和状态同步的整个开发流程。目标是减少重复决策,确保一致性,并让代理能够自信地进行编码,同时遵守用户现有的库和配置偏好。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/kartikk-k/skills/tree/HEAD/skills/web-app-nextjs

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Next.js 网页应用编码规范 是做什么的?

将这些规范应用于所请求的工作。用户的显式指令优先于本技能中的任何规则或默认设置。保留现有的产品需求和集成;应用本技能不授权进行不相关的迁移或安装每个可选的依赖项。

在实施之前,检查项目指令、已安装版本和现有约定。当可用时,阅读相关的已安装 Next.js 文档,特别是 node_modules/next/dist/docs/;否则,查阅与版本匹配的官方文档。不要假设其他版本的框架 API。

默认值和条件特性

领域默认值覆盖或激活规则
框架Next.js App Router 和严格的 TypeScript尊重项目选择的版本和用户的明确选择。
组件Base UI + Tailwind CSS,封装在本地组件中支持 Radix、shadcn/ui 或其他请求的库。保留相同的组件组织方式。
变体class-variance-authority 和一个可识别 Tailwind 的类合并辅助函数重用已建立的等效工具。
API 传输Axios,封装在自定义的 APIClient 和 ServerAPIClient 抽象之后现有的自定义客户端、fetch 或其他请求的传输方式都有效。保持契约独立于传输方式。
验证Zod 模式,带有推断出的 TypeScript 类型默认在外部数据边界需要;仅在用户明确指示时更改。
远程客户端数据TanStack Query,带有一个共享的查询包装器重用请求的等效工具。
共享交互状态Zustand对单个组件拥有的状态使用本地 React 状态。尊重明确的替代方案。
身份验证当请求身份验证时,使用与提供商无关的电子邮件 OTP 流程遵循选定的提供商和请求的登录方法;不要隐式选择或提供提供商。
跨标签同步对相关的共享变更事件启用可按需选择或移除。仅在跨标签一致性有用时添加事件。
日志和追踪遵循现有的日志系统如果不存在,除非被请求,否则不要引入、提议或提及日志/追踪基础设施。
模拟数据和仿真关闭仅在用户请求模拟数据或开发/测试仿真时添加。
开发工具独立的仅开发模块仅在相关或被请求时添加工具;不规定特定的工具或覆盖层。

当需要决策时,选择默认值;不要询问每个默认值。不要在不需要它们的特性中引入身份验证、状态存储、同步或基础设施。

必需的文件夹结构

对新代码使用此组织方式。随着职责变得必要而创建目录,而不是用空的占位符填充项目。在现有项目中,避免在没有迁移请求的情况下移动不相关的文件。

src/
├── app/                    路由、布局、API 端点
├── components/
│   ├── ui/                 共享的 UI 原语
│   ├── standalone/         可复用的领域组件
│   ├── pages/<page>/       特定页面的组件
│   └── <feature>/          特性子系统
├── controllers/
│   ├── client/<domain>/    浏览器 API 调用
│   └── server/<domain>/    服务器端 API 调用
├── providers/              React 上下文提供者
├── stores/                 Zustand 存储
├── hooks/                  共享的 React 钩子
├── helpers/                身份验证/访问及其他工作流
├── lib/                    API 客户端、缓存、日志、同步
├── db/                     数据库/Redis 客户端
├── utils/                  小型工具和常量
└── assets/icons/           源 SVG 文件

基础设施的文件夹名称描述的是放置位置,而不是必需的服务。此结构不暗示任何数据库、Redis、缓存或日志栈。

  • 将领域代码组织在这些职责文件夹下;避免用不同的顶层架构替换它们。
  • 使用 PascalCase 命名组件文件,useSomething.ts 命名钩子(仅当包含 JSX 时使用 .tsx),以及类似 updateName.ts 的驼峰命名操作文件。
  • 使用描述性领域和特性目录。保持框架文件名如 page.tsx、layout.tsx 和 route.ts 不变。
  • 使用 index.ts 组装控制器命名空间,使用 index.tsx 作为多文件组件的入口点。
  • 使用 @/ 进行根目录在 src 的导入;对紧密的内部同级文件使用相对导入。对仅类型依赖使用 import type。
  • 避免临时副本、备份文件夹、重复实现以及职责不清的汇总文件。

路由和组件组合

保持 app/ 专注于路由、布局组合、请求处理和初始数据加载。当布局边界有用时,使用类似 (auth)、(no-auth) 和 (settings) 的路由组。路由组名称本身不提供访问保护。

保持页面精简:加载初始数据,解决页面级别的决策,并从 components/pages/<page>/ 或相关的特性目录渲染组件。服务器组件是默认选择;为浏览器状态、事件和 API 引入客户端边界。

组件放置:

  • components/ui/:与领域无关的原语和所选 UI 库的包装器。
  • components/standalone/:跨页面或特性使用的可复用领域控件。
  • components/pages/<page>/:特定于一个页面的组件。
  • components/<feature>/:跨路由共享的较大子系统,例如导航或命令界面。

将复杂的组件拆分为聚焦的文件。 这是一个核心约定。一个大型组件应有一个目录,其中包含一个 index.tsx 组合入口点,以及根据需要命名的支撑组件、标签页、行、钩子和类型。不要在一个文件中累积多个大的子组件和不相关的副作用。根据职责而不是任意行数限制进行拆分;避免将琐碎的标记碎片化。

将交互机制保留在特性本地的钩子或聚焦的处理组件中:键盘导航、选择、事件订阅、拖拽行为和视图协调。当钩子被共享时,将其移动到 src/hooks/。保持输入快捷键的作用域,使其不干扰可编辑控件;在卸载时清理监听器。

在本地包装原始库,并暴露可组合的部分,例如 Dialog、DialogTrigger、DialogContent 和 DialogTitle。保留可访问的语义、焦点行为以及实际库的组合 API。不要在不检查兼容性的情况下将 Radix 特定的属性复制到 Base UI 包装器中。

从基础原始组件或原生元素中获取包装器属性的类型,并仅添加所需的自定义属性。对尺寸、强调和状态使用类型化变体。以可预测的方式合并消费者类,并根据安装的 React/库 API 保留 ref 支持。

使用连接到 Tailwind 实用程序的语义 CSS 变量。保持主题值集中化,组件变体本地化。此技能规定的是样式组织,而不是视觉主题、间距比例或产品设计。

在 app/providers.tsx 中组合相关的上下文提供者;将其实现保留在 providers/ 中。将仅浏览器的提供者放在明确的客户端边界之后,避免将开发工具初始化与正常的提供者职责混在一起。

控制器和 HTTP 客户端

在 controllers/client/<domain>/ 或 controllers/server/<domain>/ 下,每个文件一个操作。通过 index.ts 文件组装嵌套的命名空间对象,例如:

ClientController.org.members.list()
ClientController.profile.updateName(input)
ServerController.org.info()

控制器提供类型化操作和端点构造。组件应调用控制器,而不是组装 URL 或传输配置。控制器保持精简;将可复用的工作流逻辑放在 helpers/ 中,将基础设施放在 lib/ 中。

保持浏览器和服务器 HTTP 客户端分离:

  • lib/APIClient.ts:浏览器安全的传输、查询/正文序列化、响应处理和取消支持。
  • lib/ServerAPIClient.ts:仅服务器传输、可信的基础 URL 和请求范围的凭据/会话上下文。
  • 将服务器密钥和仅服务器的依赖项排除在浏览器导入之外。永远不要将每个用户的凭据保存在共享的可变单例中。
  • 当服务器控制器调用应用程序端点时,仅将所需的凭据转发到可信目的地。不要信任调用者提供的身份标头作为身份验证。
  • 一致地规范化传输错误、非 2xx 响应和应用程序错误。保留有意义的状态/代码信息。不要将失败的请求变成成功的空数据。
  • 通过控制器和传输层支持 AbortSignal。取消不应触发错误通知或重试。

API 模式、契约和处理程序

Zod 模式是输入的默认事实来源。为正文、查询和路径参数定义具体的模式,包括格式、允许的值、长度/范围限制、可选性和有意的强制转换。在使用外部值之前进行验证。避免使用 z.any()、宽泛的记录、类型转换和非空断言作为契约的替代。对每个边界有意地处理未知键。

默认情况下,将特定于方法的模式以及导出的请求/查询/响应类型与关联路由放在一起。使用 z.infer 推断请求类型;定义显式的响应契约,并在需要运行时验证的地方使用响应模式。在控制器和组件中重用这些类型,而不是重新声明它们。如果运行时契约必须与浏览器代码共享,或者路由模块的导出约束要求这样做,则使用相邻的 schema.ts 或 types.ts 和仅类型导入,以便服务器实现永远不会被拉入客户端包。

使用一致的区分响应信封:

type APIResponse<T> =
  | { success: true; data: T; error: null }
  | {
      success: false;
      data: null;
      error: { code: string; message: string; details?: unknown };
    };

在使用时,将 details 缩小为明确的、安全的验证/错误详情类型。保持所有分支的 HTTP 状态码一致。不要在生成环境的响应中暴露内部的异常或堆栈跟踪。

将多方法路由组织为特定于方法的模式/类型部分和函数,例如 get_request 和 post_request。通过 lib/api/APIRequestHandler.ts 中的 withAPIRequestHandler() 导出处理程序。

维护 lib/api/error/,包含一个 APIError 类和一个按领域组织、类型化的错误代码、消息和状态目录。共享处理程序将验证失败、领域错误和意外失败映射到相同的响应契约。仅在上面的日志条件下集成日志记录。

将重复的组织查询、身份解析、查询解析和访问工作流提取到 helpers/ 中。将小的纯转换和常量保留在 utils/ 中。将较大的集成拆分为聚焦的工作流文件;路由应编排这些步骤,而不是包含整个集成。

数据获取和状态

在适当的时候通过服务器控制器加载初始页面数据,并将类型化的、可序列化的属性传递给交互式组件。对独立的请求使用 Promise.all;保留对依赖操作的处理顺序。

使用 TanStack Query 进行远程客户端数据。提供共享的默认值和一个 useApiQuery 包装器,该包装器解包 API 信封、传播错误并转发查询取消信号。查询键必须包含相关的身份/资源/过滤器输入。在每个请求范围内创建服务器端查询客户端;在变更后失效或更新受影响的查询条目。

对共享的交互状态(例如选择、筛选器、面板和命令状态)使用特定于特性的 Zustand 存储。订阅单个切片或使用浅层选择来获取分组值。保持设置器/操作的显式性,并在其拥有的资源更改时重置状态。当 Query 已拥有远程数据时,避免在 Zustand 中维护第二个独立的副本。

对组件拥有的输入、脏状态、验证消息和保存状态使用本地状态。防止重复提交并在失败时保留用户输入。没有必需的表单库。

取消已过时的搜索/过滤请求并在清理时中止。防止过时的完成更新新资源。仅将实际的取消归类为取消;不要静默地吞掉网络故障。

对于大型列表,在有用时应用虚拟化和分页,并包含加载、空、错误、重试和列表结束状态。当资源/过滤器更改时,适当地重置分页和选择。保持键盘导航与虚拟化行一致。

可选的跨标签同步

默认情况下为相关的共享变更启用同步,除非用户禁用它。不要将其添加到没有同步需求的隔离状态。

在 lib/local-sync/ 中使用一个类型化的事件注册表,在 events/ 下使用领域事件模式,以及一个共享的 hooks/useLocalSync.ts 钩子。从 Zod 推断负载类型,并验证传出和传入的消息。

存储事件实现应通知其他标签页和同标签页的监听器,使用瞬态事件负载而不是持久化的应用状态,并清理监听器。现有的等效传输方式也是可接受的。防止回显循环和重复处理;不要同步密钥。事件应更新或失效规范的拥有者状态。跨标签事件是 UI 协调,而不是授权或保证的交付。

身份验证和授权

当身份验证在范围内时应用此部分。保持实现与提供商无关,并适应选定的服务或自定义后端。

  • 对于电子邮件 OTP 身份验证,将发送代码、验证代码和注销流程分开。

…

(已为翻译截断)

相关技能