首页 全部提示词 Web Development

React / Next.js 项目通用指令

Universal Instructions for React / Next.js Projects

文本 @ramazan747 13,051 字符 在 prompts.chat 查看 译文有问题?

中文译文

React / Next.js 项目通用指令

用途:使用 React + TypeScript、Next.js + TypeScript 和 Tailwind CSS 开发各类项目时的通用规则。 用法:将本文件作为 AGENTS.md、CLAUDE.md 或 PROJECT_RULES.md 放在新项目根目录,或将其作为 AI 智能体的基础指令集使用。 重要:这些指令不包含产品专属规则。请将所有与单个项目相关的内容放在单独的 PROJECT_RULES.md 文件中。


1. 核心原则

构建一个可上生产环境的应用程序,而不是一堆互不相连的组件。

始终遵循以下顺序:

  1. 审查当前项目结构、package.json、路由、UI 基础组件、store、hook、schema 和项目规则。
  2. 找出可复用的现有 action、helper、schema 和组件。
  3. 确定完成任务所需的最小改动。
  4. 保留现有行为。
  5. 端到端实现每个新功能:模型、校验、UI、存储/导入/导出、边界情况和验证。
  6. 运行相关检查并如实报告结果。

除非确实必要,否则不要添加依赖、抽象、全局 store 或架构层。 UI 工作默认使用 shadcn/ui。没有明确理由,不要在其之上再添加另一套 UI 套件。


2. 在 React 和 Next.js 之间做选择

当项目需要以下内容时,使用 Next.js:

  • 路由;
  • SEO;
  • SSR / 服务端组件;
  • Server Actions;
  • Route Handlers / API 路由;
  • 身份认证;
  • 数据库访问;
  • 私有环境变量;
  • 内容发布。

在以下情况下使用 React + Vite:

  • 应用程序完全是客户端;
  • 不需要 SEO;
  • 它是本地工具、仪表盘、编辑器、管理面板或类桌面 UI;
  • 服务器已作为独立服务存在。

不要仅仅因为 Next.js 流行就选择它。没有具体理由,不要添加 Redux、Zustand、React Query、表单库或另一套 UI 套件。


3. 默认技术栈与检查

默认使用以下内容:

  • React;
  • 严格模式下的 TypeScript;
  • Tailwind CSS;
  • shadcn/ui 作为实现简洁设计和快速界面开发的必需 UI 方案;
  • Lucide React 或当前 shadcn 配置所使用的图标库;
  • ESLint;
  • 共享的 cn() helper;
  • 对外部数据进行运行时校验;
  • 可访问的 HTML 元素。

将 shadcn/ui 作为 UI 基础组件的主要来源:按钮、输入框、选择器、对话框、抽屉、下拉菜单、工具提示、标签页、轮播、卡片、徽章、骨架屏、滚动区域以及其他所需组件。仅当 shadcn 未提供合适组件,或项目已有稳定的本地基础组件时,才创建自定义基础组件。

对于 MVP,先从 mock/JSON/localStorage 数据开始,优先验证本地用户流程。待 UI、模型和流程清晰后,最后再添加后端、数据库、支付、身份认证和外部集成。

代码变更后,至少运行以下命令:

npm run typecheck
npm run lint
npm run build

如果这些命令未运行或运行出错,就不要声称项目可以正常工作。


4. 架构

对于预期会持续增长的 Next.js 项目,默认将源代码放在 src/ 内:src/app、src/components、src/lib、src/data、src/hooks 和 src/features。将根级别的支持文件夹和文件(public、配置文件、锁文件和 README)保留在项目根目录中。

对于小型项目,以下结构是可以接受的:

src/
  app/ or pages/
  components/
  features/
  lib/
  shared/

对于中型和大型项目,采用类似 FSD 的方法:

src/
  app/       # bootstrap, providers, layouts, routes
  views/     # page-level composition
  widgets/   # large UI blocks
  features/  # user workflows
  entities/  # domain model
  shared/    # generic helpers, config, thin wrappers around shadcn/ui

导入方向:

app/views -> widgets -> features -> entities -> shared

不要:

  • 将 widgets 导入到 features 中;
  • 将业务逻辑放在 shared 中;
  • 把 shared/lib 变成无关函数的堆放场;
  • 在多个 UI 组件中重复变更逻辑;
  • 当某个模块暴露了公共 API 时,仍对其内部进行深层导入。

5. 公共 API

每个 feature、entity 或 shared UI 文件夹在被外部使用时,都应通过 index.ts 暴露清晰的公共 API。对于 shadcn 基础组件,公共 API 通常已经位于 components/ui/* 或项目本地的 UI 层中。

好的:

import { createTask } from "@/features/create-task";

差:

import { createTask } from "@/features/create-task/model/createTask";

例外:同一功能或实体内部的代码。


6. TypeScript

要求:

  • 启用 strict: true;
  • 除隔离的互操作代码外,不使用 any;
  • 不用 as 断言掩盖类型错误;
  • 对复杂状态使用可辨识联合;
  • 用 schema 校验运行时 JSON;
  • 没有有意义的理由,不创建多个相同的类型。

状态类型示例:

type LoadState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; message: string };

7. React 状态与副作用

把状态存放在它真正该待的地方:

状态类型 存放位置
局部 UI useState、useReducer
URL 状态 路由/搜索参数
服务端状态 服务端渲染或缓存/查询层
表单状态 表单 hook/库
全局 UI 必要时使用一个小型 store
领域状态 当状态在多个流程间共享时,使用实体/store

不要把以下内容放进全局 store:

  • 悬停状态;
  • 单个下拉框的状态;
  • 单个输入框的草稿值;
  • 单个模态框的状态;
  • 某个组件临时选中的标签页。

使用 useEffect 与外部系统同步:

  • 浏览器 API;
  • 定时器;
  • 订阅;
  • 外部 store;
  • DOM 集成。

不要用 useEffect 处理派生值。

反例:

const [fullName, setFullName] = useState("");

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

好:

const fullName = `${firstName} ${lastName}`;

8. Next.js 边界

在 App Router 中,组件默认是服务端组件。

仅在你需要以下内容的地方添加 "use client":

  • 事件处理器;
  • 本地状态;
  • 副作用;
  • window、document 或 localStorage;
  • 拖放;
  • contenteditable;
  • 仅客户端库。

没有明确需求时,不要将整个布局设为客户端组件。

仅服务端代码包括:

  • 数据库访问;
  • 身份验证;
  • 私有 API 客户端;
  • 机密环境变量;
  • webhooks;
  • 访问检查。

绝不将仅服务端模块导入客户端组件。


9. 运行时校验与迁移

在边界处校验所有外部数据:

  • 请求体;
  • 表单数据;
  • URL/搜索参数;
  • 上传的文件;
  • 导入的 JSON;
  • localStorage/IndexedDB 数据;
  • 来自外部 API 的响应。

添加新的模型字段时,更新整个生命周期:

  1. TypeScript 类型。
  2. 运行时 schema。
  3. 工厂/默认值。
  4. 针对旧数据的解析器/迁移。
  5. 规范化辅助函数。
  6. 导入/导出。
  7. 搜索/过滤索引(如果该字段应可搜索)。
  8. 撤销/重做快照(如果用户可以编辑该字段)。
  9. 用于创建、编辑和清除该字段的 UI。
  10. 边界情况与检查。

示例:

return {
  ...item,
  status: item.status ?? "active",
  tags: normalizeTags(item.tags),
  dueDate: normalizeDate(item.dueDate),
};

不要在 UI 中只添加模型字段。


10. 表单

每个表单必须包含:

  • 校验 schema;
  • 字段错误;
  • 提交中/加载状态;
  • 提交时禁用提交按钮;
  • 防止重复提交;
  • 错误状态;
  • 成功行为;
  • 重置/草稿行为(如适用)。

如果表单只在请求完全成功时才能正常工作,那它就不算完整。


11. shadcn/ui 与共享 UI

默认使用 shadcn/ui 来快速构建干净、一致的界面。

规则:

  • 首先检查所需组件是否存在于 shadcn 注册表中;
  • 通过 CLI 或项目既定的本地方式添加 shadcn 组件;
  • 如果 shadcn 已覆盖该用例,不要创建自定义的 Button、Input、Modal、Dropdown、Tooltip、Tabs 或 Card;
  • 通过 className、变体和组合来适配 shadcn 组件,而不是复制相似的组件;
  • 将业务组件与基础组件分开:components/marketplace、features/*/ui、widgets/* 或 entities/*/ui;
  • 在 components/ui 或 shared/ui 中只保留 shadcn 基础组件和轻量可复用封装;
  • 不要在那里放置产品特定的业务组件;
  • 如果 shadcn 未提供某个组件,创建一个与当前 shadcn 配置一致的最小本地封装。

用于生产力界面的 shadcn 基础组件集:

button
input
select
textarea
checkbox
switch
dialog
sheet
dropdown-menu
popover
tooltip
tabs
card
badge
avatar
separator
scroll-area
skeleton
carousel
accordion
collapsible
hover-card

对于市场、聊天和支持流程,还要为这些较新的 shadcn 组件做好规划:

message
message-scroller
attachment
marker

始终使用 cn():

export function cn(...values: Array<string | false | null | undefined>) {
  return values.filter(Boolean).join(" ");
}

12. 选择合适的 UI 界面层

在添加新工具之前,选择合适的界面层:

功能规模 放置位置 示例
1-5 个快速设置 上下文菜单 / 下拉菜单 / 弹出框 状态、截止日期、标签
5-12 个分组设置 分区、可滚动的弹出框 实体属性、紧凑筛选器
大型数据集或批量操作 侧边栏 / 抽屉 筛选器、工具面板
复杂表单或危险操作 模态框 导入/导出、删除确认
常驻工作区 专用视图/页面/小组件 仪表盘、日历、编辑器

规则:

如果某个控件只是偶尔使用,就把它放在菜单里。 如果某个控件需要频繁使用,就让它常驻在主界面上。 如果某个控件复杂且冗长,就把它移到侧边栏或模态框中。

不要将一小群控件变成页面上的一大块卡片。在生产力界面中,这会浪费宝贵的空间。


13. 为编辑器、仪表盘和工作区打造紧凑 UI

在生产力应用中,主要内容必须始终是焦点。

要求:

  • 标题、正文、看板或编辑器绝不能被次要控件向下挤压;
  • 实体属性通常应从标题旁的图标按钮打开;
  • 设置按钮必须带有 aria-label;
  • 重要状态可以显示为一个小徽章;
  • 创建/添加操作必须出现在清晰的上下文中;
  • 以侧边栏为主的流程必须包含移动端友好的菜单或切换器;
  • 不要让生产力工具看起来像一个落地页。

反例:

${largepropertiescard}
  <Select>Status</Select>
  <Select>Task</Select>
  <Input>Date</Input>
  <Input>Tags</Input>
</LargePropertiesCard>

好:

${titlerow}
  <TitleInput />
  <PropertiesMenu />
</TitleRow>

14. 浮层、下拉菜单、弹出框和上下文菜单

每个菜单都必须表现为真正的浮层。

规则:

  • 如果菜单可能超出其容器,请通过 createPortal(..., document.body) 渲染它;
  • 使用 position: fixed 或可靠定位辅助工具;
  • 设置显式的 z-index;
  • 使用不透明的 backgroundColor;
  • 不要只依赖半透明的 bg-black/50 背景或模糊;
  • 添加边框、ring 或阴影;
  • 设置 max-height 和 overflow-y-auto;
  • 按 Escape 关闭;
  • 点击/轻触外部时关闭;
  • 防止页面文本透出或渲染到菜单之上;
  • hover 和 active 状态不得改变条目的尺寸。

最小浮层样式:

<div
  role="menu"
  className="rounded-2xl border p-2 shadow-2xl"
  style=${backgroundcolor:"#151a21",
    boxShadow: "0 24px 70px rgb(0 0 0 / 78%)",
    isolation: "isolate",
    zIndex: 1000,}
>
  ...
</div>

如果菜单背景渲染不正确,或内容出现在其上方,请检查:

  • portal;
  • position;
  • z-index;
  • 父级层叠上下文;
  • isolation;
  • opacity/background;
  • 父级 overflow/clipping。

15. 菜单中的选项列表

菜单中的任务、项目、用户、标签或其他选项列表,绝不能看起来像一堵密不透风的文字墙。

对于两行条目:

  • 使用 40-44px 的 min-height;
  • 在图标、文本和勾选标记之间加入 gap;
  • 使用垂直内边距,例如 py-1.5;
  • 让标题和元数据使用不同的行高;
  • 在标题和元数据之间添加 mt-0.5;
  • 对包含文本的父级应用 min-w-0;
  • 对标题和元数据应用 truncate;
  • 对勾选标记和图标应用 shrink-0。

示例:

<button className="flex min-h-11 items-center gap-2.5 rounded-lg px-2.5 py-1.5">
  <span className="min-w-0 flex-1">
    <span className="block truncate font-medium leading-5">${title}</span>
    <span className="mt-0.5 block truncate text-xs leading-4 text-muted">
      {meta}
    </span>
  </span>
  {isActive ? <Check className="shrink-0" /> : null}
</button>

16. 长文本与溢出

任何用户提供的文本都可能包含一个没有空格的长单词。

对于编辑器、contenteditable 元素、Markdown、卡片标题和评论:

  • 在 flex/grid 子元素上使用 min-w-0;
  • 使用当前 Tailwind 工具类来换行长单词;
  • 在较新的 Tailwind 版本中,break-words 可能写作 wrap-break-word;
  • 在使用换行、溢出、text-wrap、grid、间距或任意值类之前,查阅项目当前 Tailwind 版本的文档;
  • 如果某个元素位于 flex 容器内,且长文本破坏了它的宽度,检查 wrap-anywhere 是否合适;
  • 对卡片中的短行使用 truncate;
  • 对正文文本进行换行,而不是允许水平溢出;
  • 文本绝不能渲染到菜单、弹出框或模态框之上;
  • 用一个不含空格的长字符串进行测试。

对于可编辑块:

className = "min-w-0 wrap-break-word whitespace-pre-wrap";

如果项目使用的是较旧的 Tailwind 版本,其中 wrap-break-word 不可用,请检查已安装的 Tailwind 版本以及官方文档或版本说明,然后使用受支持的等效方案:break-words、任意值或 CSS 属性。

对于徽章:

className = "inline-flex whitespace-nowrap";

徽章绝不能垂直压缩文本。如果放不下,就把它移到新行,或使用带明确、可理解宽度的 truncate。


17. Tailwind CSS:核实当前类名

AI 智能体在使用新的或可能依赖版本的类之前,必须检查项目中安装的 Tailwind 版本。

流程:

  1. 检查 package.json 和锁文件。
  2. 确定 Tailwind 主版本。
  3. 如果某个类在不同版本之间可能有差异,查阅该确切版本的官方文档。
  4. 不要未经核实就机械地替换类。
  5. 使用任意值时,确认它已包含在构建输出中。

尤其要注意:

  • break-words / wrap-break-word / wrap-anywhere;
  • text-wrap、text-balance 和 text-pretty;
  • overflow-*;
  • size-*;
  • 任意颜色,例如 bg-[#151a21];
  • 任意阴影;
  • 任意网格模板;
  • 动态类名。

不要像这样构建动态 Tailwind 类:

const color = "red";
return <div className={`bg-${color}-500`} />;

Tailwind 在构建时可能检测不到该类。请使用映射:

const colorClassName = {
  danger: "bg-red-500",
  success: "bg-emerald-500",
}${variant};

如果重要的遮罩背景不能依赖 Tailwind 的构建产物,使用内联 style.backgroundColor 是可以接受的。


18. 布局与侧边栏折叠

折叠侧边栏或抽屉绝不能改变页面高度,也不能留下空白块。

规则:

  • 应用外壳:h-dvh min-h-dvh overflow-hidden;
  • 内部区域:flex min-h-0 flex-1 overflow-hidden;
  • 仅在合适的区域通过 overflow-y-auto 启用滚动;
  • 折叠时,改变宽度/flex-basis 而不是高度;
  • 折叠后的侧边栏必须具有稳定的宽度;
  • 提供清晰的控件用于恢复侧边栏;
  • 破坏性或创建性操作绝不能作为孤立的按钮存在而缺乏上下文;
  • 偏好设置可以持久化到 localStorage。

示例:

<main className="flex h-dvh min-h-dvh flex-col overflow-hidden">
  <div className="flex min-h-0 flex-1 overflow-hidden">
    <Sidebar className="h-full min-h-0 shrink-0" />
    <section className="min-h-0 flex-1 overflow-y-auto" />
  </div>
</main>

19. 浏览器 API 与 localStorage

在 Next.js 中,浏览器 API 仅在客户端组件中可用。

规则:

  • 使用 localStorage、window、document、拖放或 contenteditable 的文件必须包含 "use client";
  • 不要在服务端组件中读取 localStorage;
  • 不要因初始值不同而导致水合错误;
  • 将存储操作包裹在 try/catch 中;
  • 存储失败绝不能破坏 UI;
  • 在重新加载后验证持久化的 UI 偏好设置;
  • 构建绝不能因 window is not defined 而失败。

示例:

const toggle = useCallback(() => {
  setIsCollapsed((current) => {
    const next = !current;

    try {
      window.localStorage.setItem(KEY, next ? "true" : "false");
    } catch {
      // UI still works without browser storage.
    }

    return next;
  });
}, []);

验证以下内容:

  • 默认状态在空存储下可正常工作;
  • 重新加载后状态得以保留;
  • 隐私模式或存储错误不会导致页面崩溃;
  • 构建不会因 window is not defined 而失败。

20. 工具之间的关系

如果一个实体链接到另一个实体,该关系必须是真实的:

  • 将其存储在模型中;
  • 在 UI 中展示它;
  • 点击它会打开所链接的实体;
  • 创建相关实体时,立即保存该关系;
  • 在导入/导出过程中保留该关系;
  • 在有意义时,将该关系纳入搜索/筛选行为;
  • 如果相关实体被删除,在 UI 中显示回退内容。

如果关系未被持久化,就不要创建一个装饰性的“链接”按钮。


21. 统一的领域操作

每个用户操作都必须有单一的事实来源。

不要:

  • 通过斜杠菜单以一种方式创建实体;
  • 通过工具栏以另一种方式创建它;
  • 通过命令面板绕过验证;
  • 在上下文菜单中重复变更逻辑。

而应该:

  • 将领域操作保留在一个地方;
  • 让 UI 组件调用该操作;
  • 对每个入口点使用相同的验证和约束。

23. 无障碍

必需:

  • 对操作使用 <button>;
  • 对导航使用 <a>;
  • 为仅含图标的按钮添加 aria-label;
  • 为输入提供标签;
  • 显示可见的焦点状态;
  • 支持键盘导航;
  • 在 Escape 时关闭模态框和弹出层;
  • 在外部点击时关闭弹出层;
  • 在模态框中使用焦点陷阱;
  • 不要将颜色作为传达含义的唯一方式;
  • 不要用 ${div_onclick} 替换 <button>。

24. 加载、空状态和错误状态

数据驱动的屏幕必须考虑:

  • 加载中;
  • 成功;
  • 空状态;
  • 权限被拒绝;
  • 网络错误;
  • 服务器错误;
  • 重试。

没有任何说明的空白屏幕是一个 bug。

25. 安全

要求:

  • 密钥只保存在服务器上;
  • 使用运行时校验;
  • 在服务器上强制执行访问控制;
  • 校验文件的 MIME 类型和大小;
  • 对用户提供的 HTML 进行净化;
  • 在没有净化器的情况下不要使用 dangerouslySetInnerHTML;
  • 不要记录令牌或个人数据;
  • 不要信任浏览器提供的 role 或 userId 值。

26. 性能

先测量,再优化。

使用:

  • 对重型编辑器、图表、地图和 PDF 模块使用动态导入;
  • 图片优化;
  • 对大型列表使用虚拟化;
  • 对搜索使用中止/过期请求保护;
  • 使用选择器减少重渲染。

不要在没有理由的情况下添加记忆化。


27. 用真实内容测试设计

关于界面质量的更多指导,你可以参考:

在打磨排版、悬停状态、阴影、边框、间距、视觉对齐、微交互以及界面整体手感时,这个资源很有用。

在完成 UI 任务之前,用以下内容测试它:

  • 一个没有空格的长单词;
  • 一个很长的俄语标题;
  • 一个短标题;
  • 一个空标题;
  • 多个标签;
  • 一个很长的列表/分类名称;
  • 下拉菜单中的多个选项;
  • 激活和非激活状态;
  • 一个日期和一个缺失的日期。

验证:

  • 没有任何内容重叠;
  • 覆盖层遮住底层内容;
  • 文字不会透过菜单显示出来;
  • 徽章不会在垂直方向压缩文字;
  • 元素之间不会相互拥挤;
  • 滚动条不会遮住重要文字;
  • 悬停和聚焦状态易于阅读;
  • 桌面端和移动端宽度看起来都正确。

28. 更改后的检查

代码更改后,运行:

npm run typecheck
npm run lint
npm run build

如果 UI 发生了变更:

  • 在浏览器中打开页面;
  • 走通主要用户流程;
  • 测试键盘和鼠标交互;
  • 测试 Escape 和点击外部区域的行为;
  • 测试重新加载;
  • 测试长文本;
  • 测试移动端视口宽度;
  • 如果视觉层发生了变更,截图。

如果无法进行浏览器验证,请明确说明。不要把 typecheck 当作视觉验证。


29. Git 与工作区

在做出更改之前,先检查当前状态:

git status --short

规则:

  • 未经明确请求,不要回退他人的更改;
  • 未经明确许可,不要使用破坏性命令;
  • 不要进行无关的重构;
  • 除非用户要求,不要自动提交;
  • 不要不必要地更改行尾符或重新格式化整个项目。

30. 最终报告

在最终回复中,说明:

  • 更改了什么;
  • 哪些文件是重要的;
  • 运行了哪些检查;
  • 哪些内容无法验证;
  • 还剩下哪些风险。

保持报告简洁且诚实。

English original

Universal Instructions for React / Next.js Projects

Purpose: General rules for developing various projects with React + TypeScript, Next.js + TypeScript, and Tailwind CSS. Usage: Place this file in the root of a new project as AGENTS.md, CLAUDE.md, or PROJECT_RULES.md, or use it as a base instruction set for an AI agent. Important: These instructions do not contain product-specific rules. Keep everything related to an individual project in a separate PROJECT_RULES.md file.


1. Core Principle

Build a production-ready application, not a collection of disconnected components.

Always follow this sequence:

  1. Review the current project structure, package.json, routing, UI primitives, stores, hooks, schemas, and project rules.
  2. Find existing actions, helpers, schemas, and components that can be reused.
  3. Identify the smallest change required for the task.
  4. Preserve existing behavior.
  5. Implement each new feature end to end: model, validation, UI, storage/import/export, edge cases, and verification.
  6. Run the relevant checks and report the results honestly.

Do not add dependencies, abstractions, a global store, or an architectural layer unless they are genuinely necessary. Use shadcn/ui by default for UI work. Do not add another UI kit on top of it without a clear reason.


2. Choosing Between React and Next.js

Use Next.js when the project needs:

  • routing;
  • SEO;
  • SSR / Server Components;
  • Server Actions;
  • Route Handlers / API routes;
  • authentication;
  • database access;
  • private environment variables;
  • content publishing.

Use React + Vite when:

  • the application is entirely client-side;
  • SEO is not required;
  • it is a local tool, dashboard, editor, admin panel, or desktop-like UI;
  • the server already exists as a separate service.

Do not choose Next.js simply because it is popular. Do not add Redux, Zustand, React Query, a form library, or another UI kit without a specific reason.


3. Default Stack and Checks

Use the following by default:

  • React;
  • TypeScript in strict mode;
  • Tailwind CSS;
  • shadcn/ui as the required UI approach for clean design and rapid interface development;
  • Lucide React or the icon library used by the current shadcn configuration;
  • ESLint;
  • a shared cn() helper;
  • runtime validation for external data;
  • accessible HTML elements.

Use shadcn/ui as the primary source of UI primitives: buttons, inputs, selects, dialogs, sheets, dropdowns, tooltips, tabs, carousels, cards, badges, skeletons, scroll areas, and other required components. Create custom primitives only when shadcn does not provide a suitable component or when the project already has a stable local primitive.

For an MVP, begin with mock/JSON/localStorage data and validate local user flows first. Add the backend, database, payments, authentication, and external integrations last, once the UI, models, and flows are clear.

At a minimum, run these commands after code changes:

npm run typecheck
npm run lint
npm run build

Do not claim that the project works if these commands were not run or completed with errors.


4. Architecture

For Next.js projects expected to grow, keep source code inside src/ by default: src/app, src/components, src/lib, src/data, src/hooks, and src/features. Keep root-level support folders and files (public, configuration files, lockfiles, and README) in the project root.

For small projects, the following structure is acceptable:

src/
  app/ or pages/
  components/
  features/
  lib/
  shared/

For medium and large projects, use an FSD-like approach:

src/
  app/       # bootstrap, providers, layouts, routes
  views/     # page-level composition
  widgets/   # large UI blocks
  features/  # user workflows
  entities/  # domain model
  shared/    # generic helpers, config, thin wrappers around shadcn/ui

Import direction:

app/views -> widgets -> features -> entities -> shared

Do not:

  • import widgets into features;
  • place business logic in shared;
  • turn shared/lib into a dumping ground for unrelated functions;
  • duplicate mutation logic across multiple UI components;
  • use deep imports into another module's internals when that module exposes a public API.

5. Public API

Every feature, entity, or shared UI folder should expose a clear public API through index.ts when the module is used externally. For shadcn primitives, the public API usually already lives in components/ui/* or the project's local UI layer.

Good:

import { createTask } from "@/features/create-task";

Bad:

import { createTask } from "@/features/create-task/model/createTask";

Exception: internal code within the same feature or entity.


6. TypeScript

Required:

  • enable strict: true;
  • do not use any except in isolated interoperability code;
  • do not hide type errors with as assertions;
  • use discriminated unions for complex state;
  • validate runtime JSON with a schema;
  • do not create multiple identical types without a meaningful reason.

Example state type:

type LoadState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; message: string };

7. React State and Effects

Store state where it actually belongs:

State type Where to store it
Local UI useState, useReducer
URL state route/search parameters
Server state server rendering or a cache/query layer
Form state form hook/library
Global UI a small store when necessary
Domain state entity/store when the state is shared across multiple flows

Do not put the following in a global store:

  • hover state;
  • the state of a single dropdown;
  • the draft value of a single input;
  • the state of a single modal;
  • the temporary selected tab of one component.

Use useEffect to synchronize with external systems:

  • browser APIs;
  • timers;
  • subscriptions;
  • external stores;
  • DOM integrations.

Do not use useEffect for derived values.

Bad:

const [fullName, setFullName] = useState("");

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

Good:

const fullName = `${firstName} ${lastName}`;

8. Next.js Boundaries

In the App Router, components are Server Components by default.

Add "use client" only where you need:

  • event handlers;
  • local state;
  • effects;
  • window, document, or localStorage;
  • drag and drop;
  • contenteditable;
  • client-only libraries.

Do not make an entire layout a Client Component without a clear need.

Server-only code includes:

  • database access;
  • authentication;
  • private API clients;
  • secret environment variables;
  • webhooks;
  • access checks.

Never import a server-only module into a Client Component.


9. Runtime Validation and Migrations

Validate all external data at the boundary:

  • request bodies;
  • form data;
  • URL/search parameters;
  • uploaded files;
  • imported JSON;
  • localStorage/IndexedDB data;
  • responses from external APIs.

When adding a new model field, update the entire lifecycle:

  1. TypeScript type.
  2. Runtime schema.
  3. Factory/default values.
  4. Parser/migration for legacy data.
  5. Normalization helpers.
  6. Import/export.
  7. Search/filter indexing, if the field should be searchable.
  8. Undo/redo snapshots, if users can edit the field.
  9. UI for creating, editing, and clearing the field.
  10. Edge cases and checks.

Example:

return {
  ...item,
  status: item.status ?? "active",
  tags: normalizeTags(item.tags),
  dueDate: normalizeDate(item.dueDate),
};

Do not add a model field only in the UI.


10. Forms

Every form must include:

  • a validation schema;
  • field errors;
  • a submitting/loading state;
  • a disabled submit button while submitting;
  • protection against duplicate submissions;
  • an error state;
  • success behavior;
  • reset/draft behavior, when applicable.

A form is not complete if it works only when the request succeeds perfectly.


11. shadcn/ui and Shared UI

Use shadcn/ui by default to build clean, consistent interfaces quickly.

Rules:

  • first check whether the required component exists in the shadcn registry;
  • add shadcn components through the CLI or the project's established local method;
  • do not create a custom Button, Input, Modal, Dropdown, Tooltip, Tabs, or Card if shadcn already covers the use case;
  • adapt shadcn components through className, variants, and composition instead of copying similar components;
  • keep business components separate from primitives: components/marketplace, features/*/ui, widgets/*, or entities/*/ui;
  • keep only shadcn primitives and thin reusable wrappers in components/ui or shared/ui;
  • do not place product-specific business components there;
  • if shadcn does not provide a component, create a minimal local wrapper consistent with the current shadcn configuration.

Base set of shadcn components for productivity interfaces:

button
input
select
textarea
checkbox
switch
dialog
sheet
dropdown-menu
popover
tooltip
tabs
card
badge
avatar
separator
scroll-area
skeleton
carousel
accordion
collapsible
hover-card

For marketplace, chat, and support flows, also plan for these newer shadcn components:

message
message-scroller
attachment
marker

Always use cn():

export function cn(...values: Array<string | false | null | undefined>) {
  return values.filter(Boolean).join(" ");
}

12. Choosing the Right UI Surface

Before adding a new tool, choose the right surface:

Feature size Placement Example
1-5 quick settings context menu / dropdown / popover status, due date, tags
5-12 grouped settings sectioned, scrollable popover entity properties, compact filters
large data sets or bulk actions sidebar / drawer filters, tools panel
complex form or dangerous action modal import/export, delete confirmation
permanent workspace dedicated view/page/widget dashboard, calendar, editor

Rule:

If a control is used occasionally, keep it in a menu. If a control is used constantly, keep it visible on the main surface. If a control is complex and lengthy, move it to a sidebar or modal.

Do not turn a small group of controls into a large card on the page. In productivity interfaces, this wastes valuable space.


13. Compact UI for Editors, Dashboards, and Workspaces

In productivity applications, the primary content must remain the focus.

Required:

  • the title, body, board, or editor must not be pushed downward by secondary controls;
  • entity properties should generally open from an icon button next to the title;
  • settings buttons must have an aria-label;
  • an important status can be shown as a small badge;
  • create/add actions must appear in a clear context;
  • sidebar-heavy flows must include a mobile-friendly menu or switcher;
  • do not make a productivity tool look like a landing page.

Bad:

${largepropertiescard}
  <Select>Status</Select>
  <Select>Task</Select>
  <Input>Date</Input>
  <Input>Tags</Input>
</LargePropertiesCard>

Good:

${titlerow}
  <TitleInput />
  <PropertiesMenu />
</TitleRow>

14. Overlays, Dropdowns, Popovers, and Context Menus

Every menu must behave as a true overlay.

Rules:

  • if a menu may extend beyond its container, render it through createPortal(..., document.body);
  • use position: fixed or a reliable positioning helper;
  • set an explicit z-index;
  • use an opaque backgroundColor;
  • do not rely only on a translucent bg-black/50 background or blur;
  • add a border, ring, or shadow;
  • set max-height and overflow-y-auto;
  • close on Escape;
  • close on outside click/tap;
  • prevent page text from showing through or rendering over the menu;
  • hover and active states must not change the item's dimensions.

Minimal overlay style:

<div
  role="menu"
  className="rounded-2xl border p-2 shadow-2xl"
  style=${backgroundcolor:"#151a21",
    boxShadow: "0 24px 70px rgb(0 0 0 / 78%)",
    isolation: "isolate",
    zIndex: 1000,}
>
  ...
</div>

If the menu background does not render correctly or content appears above it, check:

  • the portal;
  • position;
  • z-index;
  • parent stacking contexts;
  • isolation;
  • opacity/background;
  • parent overflow/clipping.

15. Option Lists in Menus

A list of tasks, projects, users, tags, or other options in a menu must not look like a dense wall of text.

For a two-line item:

  • use a min-height of 40-44px;
  • include a gap between the icon, text, and checkmark;
  • use vertical padding such as py-1.5;
  • give the title and metadata different line heights;
  • add mt-0.5 between the title and metadata;
  • apply min-w-0 to the parent containing the text;
  • apply truncate to the title and metadata;
  • apply shrink-0 to checkmarks and icons.

Example:

<button className="flex min-h-11 items-center gap-2.5 rounded-lg px-2.5 py-1.5">
  <span className="min-w-0 flex-1">
    <span className="block truncate font-medium leading-5">${title}</span>
    <span className="mt-0.5 block truncate text-xs leading-4 text-muted">
      {meta}
    </span>
  </span>
  {isActive ? <Check className="shrink-0" /> : null}
</button>

16. Long Text and Overflow

Any user-provided text may contain a long word with no spaces.

For editors, contenteditable elements, Markdown, card titles, and comments:

  • use min-w-0 on flex/grid children;
  • use the current Tailwind utilities for wrapping long words;
  • in newer Tailwind versions, break-words may be written as wrap-break-word;
  • check the documentation for the project's current Tailwind version before using wrapping, overflow, text-wrap, grid, spacing, or arbitrary-value classes;
  • if an element is inside a flex container and long text breaks its width, check whether wrap-anywhere is appropriate;
  • use truncate for short lines in cards;
  • wrap body text instead of allowing horizontal overflow;
  • text must not render over a menu, popover, or modal;
  • test with a long string containing no spaces.

For an editable block:

className = "min-w-0 wrap-break-word whitespace-pre-wrap";

If the project uses an older Tailwind version where wrap-break-word is unavailable, check the installed Tailwind version and the official documentation or version notes, then use a supported equivalent: break-words, an arbitrary value, or a CSS property.

For a badge:

className = "inline-flex whitespace-nowrap";

A badge must not compress text vertically. If it does not fit, move it to a new line or use truncate with an explicit, understandable width.


17. Tailwind CSS: Verify Current Class Names

The AI agent must check the Tailwind version installed in the project before using new or potentially version-dependent classes.

Process:

  1. Inspect package.json and the lockfile.
  2. Determine the Tailwind major version.
  3. If a class may differ between versions, check the official documentation for that exact version.
  4. Do not replace classes mechanically without verification.
  5. When using an arbitrary value, confirm that it is included in the build output.

Pay particular attention to:

  • break-words / wrap-break-word / wrap-anywhere;
  • text-wrap, text-balance, and text-pretty;
  • overflow-*;
  • size-*;
  • arbitrary colors such as bg-[#151a21];
  • arbitrary shadows;
  • arbitrary grid templates;
  • dynamic class names.

Do not build dynamic Tailwind classes like this:

const color = "red";
return <div className={`bg-${color}-500`} />;

Tailwind may not detect that class during the build. Use a map:

const colorClassName = {
  danger: "bg-red-500",
  success: "bg-emerald-500",
}${variant};

If an important overlay background must not depend on Tailwind's build output, using an inline style.backgroundColor is acceptable.


18. Layout and Sidebar Collapse

Collapsing a sidebar or drawer must not change the page height or leave an empty block.

Rules:

  • app shell: h-dvh min-h-dvh overflow-hidden;
  • internal regions: flex min-h-0 flex-1 overflow-hidden;
  • enable scrolling only on the appropriate region with overflow-y-auto;
  • when collapsing, change width/flex-basis rather than height;
  • a collapsed sidebar must have a stable width;
  • provide a clear control for restoring the sidebar;
  • destructive or creation actions must not remain as isolated buttons without context;
  • preferences may be persisted in localStorage.

Example:

<main className="flex h-dvh min-h-dvh flex-col overflow-hidden">
  <div className="flex min-h-0 flex-1 overflow-hidden">
    <Sidebar className="h-full min-h-0 shrink-0" />
    <section className="min-h-0 flex-1 overflow-y-auto" />
  </div>
</main>

19. Browser APIs and localStorage

In Next.js, browser APIs are available only in Client Components.

Rules:

  • a file that uses localStorage, window, document, drag and drop, or contenteditable must include "use client";
  • do not read localStorage in a Server Component;
  • do not cause hydration errors with different initial values;
  • wrap storage operations in try/catch;
  • storage failures must not break the UI;
  • verify persisted UI preferences after a reload;
  • the build must not fail with window is not defined.

Example:

const toggle = useCallback(() => {
  setIsCollapsed((current) => {
    const next = !current;

    try {
      window.localStorage.setItem(KEY, next ? "true" : "false");
    } catch {
      // UI still works without browser storage.
    }

    return next;
  });
}, []);

Verify that:

  • the default state works with empty storage;
  • a reload preserves the state;
  • private mode or storage errors do not break the screen;
  • the build does not fail with window is not defined.

20. Relationships Between Tools

If one entity is linked to another, the relationship must be real:

  • store it in the model;
  • show it in the UI;
  • clicking it opens the linked entity;
  • when creating the related entity, save the relationship immediately;
  • preserve the relationship during import/export;
  • include the relationship in search/filter behavior when useful;
  • if the related entity is deleted, show a fallback in the UI.

Do not create a decorative "Link" button if the relationship is not persisted.


21. Unified Domain Operations

Each user operation must have a single source of truth.

Do not:

  • create an entity one way from the slash menu;
  • create it another way from the toolbar;
  • bypass validation from the command palette;
  • duplicate mutation logic in the context menu.

Instead:

  • keep the domain operation in one place;
  • have UI components call that operation;
  • use the same validation and constraints for every entry point.

23. Accessibility

Required:

  • use <button> for actions;
  • use <a> for navigation;
  • add aria-label to icon-only buttons;
  • provide labels for inputs;
  • show a visible focus state;
  • support keyboard navigation;
  • close modals and popovers on Escape;
  • close popovers on outside click;
  • use a focus trap in modals;
  • do not use color as the only way to communicate meaning;
  • do not replace <button> with ${div_onclick}.

24. Loading, Empty, and Error States

Data-driven screens must account for:

  • loading;
  • success;
  • empty state;
  • permission denied;
  • network error;
  • server error;
  • retry.

A blank screen with no explanation is a bug.


25. Security

Required:

  • keep secrets on the server only;
  • use runtime validation;
  • enforce access control on the server;
  • validate file MIME types and sizes;
  • sanitize user-provided HTML;
  • do not use dangerouslySetInnerHTML without a sanitizer;
  • do not log tokens or personal data;
  • do not trust role or userId values supplied by the browser.

26. Performance

Measure first, then optimize.

Use:

  • dynamic imports for heavy editor, chart, map, and PDF modules;
  • image optimization;
  • virtualization for large lists;
  • abort/stale-request protection for search;
  • selectors to reduce rerenders.

Do not add memoization without a reason.


27. Test the Design with Realistic Content

For additional guidance on interface quality, you may refer to:

This resource is useful when polishing typography, hover states, shadows, borders, spacing, optical alignment, micro-interactions, and the overall feel of the interface.

Before completing a UI task, test it with:

  • a long word with no spaces;
  • a long Russian title;
  • a short title;
  • an empty title;
  • multiple tags;
  • a long list/category name;
  • multiple options in a dropdown;
  • active and inactive statuses;
  • a date and a missing date.

Verify that:

  • nothing overlaps;
  • overlays cover the underlying content;
  • text does not show through menus;
  • badges do not compress text vertically;
  • elements do not crowd each other;
  • scrollbars do not cover important text;
  • hover and focus states are easy to read;
  • desktop and mobile widths both look correct.

28. Checks After Changes

After code changes, run:

npm run typecheck
npm run lint
npm run build

If the UI was changed:

  • open the page in a browser;
  • complete the primary user flow;
  • test keyboard and mouse interaction;
  • test Escape and outside-click behavior;
  • test reloading;
  • test long text;
  • test a mobile viewport width;
  • take a screenshot if the visual layer changed.

If browser verification is impossible, say so explicitly. Do not present typecheck as visual verification.


29. Git and the Working Tree

Before making changes, inspect the current state:

git status --short

Rules:

  • do not revert someone else's changes without an explicit request;
  • do not use destructive commands without explicit permission;
  • do not perform unrelated refactoring;
  • do not commit automatically unless the user asks you to;
  • do not change line endings or reformat the entire project unnecessarily.

30. Final Report

In the final response, state:

  • what changed;
  • which files are important;
  • which checks were run;
  • what could not be verified;
  • which risks remain.

Keep the report concise and honest.