Onlook 仓库 AI Agent 开发指南:Monorepo 架构约束、tRPC/Next.js 实践与最小化 Diff 原则
【免费下载链接】onlookThe Cursor for Designers • An Open-Source AI-First Design tool • Visually build, style, and edit your React App with AI项目地址: https://gitcode.com/GitHub_Trending/on/onlook
本文基于 Onlook 仓库根目录的 CLAUDE.md(Agent 指南)展开,并结合仓库源码逐一验证其规则背后的实现依据。Onlook 是一个开源的、AI-First 的 React 可视化编辑工具("The Cursor for Designers"),仓库采用 Bun workspaces 管理的 Monorepo 结构。本文面向在 Onlook 仓库内工作的自动化编码 Agent(以及希望以 Agent 友好的方式理解该仓库的开发者),系统梳理其技术栈约束、目录约定、客户端/服务端边界、tRPC 路由注册机制、环境变量治理、MobX store 生命周期等核心规则,并给出可直接执行的常用命令。读完本文,你将掌握一套"正确性优先、diff 最小化、token 高效"的仓库级 Agent 协作规范,以及每条规则对应的真实源码位置。
指南的定位与适用范围
CLAUDE.md是一份"可执行规则"(Actionable rules)文档,受众是工作于本仓库内的自动化编码 Agent。其核心目标非常明确:
- 目标:生成小而正确(small, correct)的 diff,并与项目既有架构保持一致;
- 非目标:不修改生成的产物(generated artifacts)、锁文件(lockfiles)或
node_modules。
这意味着任何 Agent 在本仓库内改动代码时,第一优先级不是"完成功能",而是"以最小范围、最小副作用的方式完成功能"。与之配套,文档还给出了上下文纪律(Context Discipline):用 ripgrep 精确搜索、只打开必要的文件、只读小段内容、避免读取node_modules/.next/大资源文件,从而在 token 受限的 Agent 环境中高效工作。
仓库全景(Repo Map)
Onlook 是一个由Bun workspaces管理的 Monorepo,这一点在根目录 package.json 中得到确认:
{ "name": "@onlook/repo", "packageManager": "bun@1.3.1", "workspaces": [ "packages/*", "apps/*", "tooling/*", "apps/web/*", "docs" ] }文档给出的核心目录映射如下:
| 职责 | 路径 |
|---|---|
| 主应用(Next.js App Router + TailwindCSS) | apps/web/client |
| API 路由(tRPC routers) | apps/web/client/src/server/api/routers/* |
| tRPC 路由聚合入口 | apps/web/client/src/server/api/root.ts |
| 共享工具包 | packages/*(如packages/utility) |
仓库中packages/下还有大量共享能力包,如packages/ui(组件库)、packages/db(Drizzle ORM 数据层)、packages/models(领域模型)、packages/parser(代码解析)、packages/code-provider(沙箱代码提供)等,它们共同支撑主应用的编辑器、AI 对话、发布等能力。根package.json中 workspace 声明还包含apps/web/*,即 web 目录下的client、preload、server都属于同一 workspace 体系。
技术栈与运行时约束
文档明确规定了本仓库的技术选型与唯一运行时:
- UI:Next.js App Router + TailwindCSS;
- API:tRPC + Zod(位于
apps/web/client/src/server/api/*); - 包管理器:仅允许 Bun,所有安装与脚本执行都必须使用 Bun,禁止 npm、yarn 或 pnpm。
根 package.json 的 scripts 全部以bun --filter ...或cd ... && bun ...形式组织,例如typecheck、test、db:push等,印证了这一约束。此外根目录存在 bun.lock 与 bunfig.toml,进一步说明 Bun 是唯一受支持的包管理器。
Agent 优先级(Agent Priorities)
在动手之前,Agent 应遵循以下优先级清单:
- 正确性优先:最小范围(minimal scope)与定向编辑(targeted edits);
- 尊重 App Router 中的客户端/服务端边界;
- 优先复用本地既有模式与现有抽象,避免引入一次性框架(one-off frameworks);
- 不修改构建产物、生成文件或锁文件;
- 所有脚本一律使用 Bun,不引入 npm/yarn;
- 在自动化环境中避免运行本地开发服务器;
- 尊重类型安全。
Next.js App Router 实践
apps/web/client是标准的 Next.js App Router 应用,文档给出以下强制规则:
Server Component 优先
- 默认使用 Server Components;只有在需要事件处理、state/effects、浏览器 API 或客户端专属库时,才添加
use client指令。 - 应用结构位于
apps/web/client/src/app/**(含page.tsx、layout.tsx、route.ts)。
客户端边界(Client Boundary)
客户端 Provider 必须位于客户端边界之后,典型例子是 apps/web/client/src/trpc/react.tsx,其首行即为'use client'。
文档强调的一个模式是:只需在功能入口处设置一个客户端边界,其内部的子组件即使使用了observer也无需重复添加use client。典型示例是 apps/web/client/src/app/project/[id]/_components/main.tsx,该文件首行'use client'后使用observer包裹了整个编辑器主界面,而其内部导入的EditorBar、Canvas、LeftPanel、RightPanel等子组件不必各自声明客户端边界。
Root Layout 作为 RSC 壳
apps/web/client/src/app/layout.tsx 是一个典型的 RSC 壳:全局样式(@/styles/globals.css与@onlook/ui/globals.css)、TRPCReactProvider、FeatureFlagsProvider、TelemetryProvider、ThemeProvider(forcedTheme="dark"保持暗色主题默认)、AuthProvider、NextIntlClientProvider依次嵌套,第三方脚本(Zaraz、RB2BLoader)通过env.NODE_ENV === 'production'门控,仅在正式环境加载——这与文档中"scripts gated by env"的描述完全一致。
MobX observer 组件必须是客户端组件
使用mobx-react-lite的observer的组件必须位于客户端边界内(含use client)。相关依赖可在 apps/web/client/package.json 中确认。
tRPC API 架构
tRPC 是 Onlook 服务端 API 的唯一通道,文档规则如下:
- Routers 位于
apps/web/client/src/server/api/routers/**,必须在apps/web/client/src/server/api/root.ts中导出; - 使用
apps/web/client/src/server/api/trpc.ts中的publicProcedure/protectedProcedure,并用 Zod 校验输入; - 序列化由 SuperJSON 处理,返回普通对象/数组;
- 客户端通过
apps/web/client/src/trpc/react.tsx(React Query + tRPC links)消费。
Router 目录组织
从源码看,apps/web/client/src/server/api/routers/按领域划分子目录:chat/(会话、消息、建议)、project/(分支、fork、frame、成员、沙箱、设置)、domain/(自定义域名与验证)、publish/(部署、发布/取消发布)、subscription/、usage/、user/、forward/(编辑器转发)等,另有code.ts、github.ts、image.ts等顶层文件。
root.ts 的手动聚合
apps/web/client/src/server/api/root.ts 是"新增 Router 必须在此注册"的直接证据——它手动把sandboxRouter、userRouter、invitationRouter、projectRouter、branchRouter、settingsRouter、chatRouter、frameRouter、userCanvasRouter、utilsRouter、memberRouter、domainRouter、githubRouter、subscriptionRouter、usageRouter、publishRouter聚合进appRouter,并导出AppRouter类型与createCaller服务端调用器。如果新增 Router 忘记在此注册,端点将不可达(这也是 Common Pitfalls 中专门列出的一条)。
trpc.ts 的过程定义
apps/web/client/src/server/api/trpc.ts 详细展示了三种过程的实现:
publicProcedure:基础过程,挂载timingMiddleware(开发环境模拟 100–500ms 随机网络延迟,帮助提前发现瀑布式请求,并打印每个路径的执行耗时);protectedProcedure:在公共过程基础上校验ctx.user与用户邮箱,未认证直接抛UNAUTHORIZED;adminProcedure:更进一步,注入createAdminClient()(Supabase service role)以绕过 RLS——注释明确警告"Use with extreme caution as it bypasses RLS policies"。
上下文通过createTRPCContext构建:先调用createClient()(服务端 Supabase 客户端)获取用户会话,再注入 Drizzledb与user。错误格式化器会捕获ZodError并展平(flatten)为前端可用的zodError字段。示例 Router(apps/web/client/src/server/api/routers/project/branch.ts)展示了典型写法:protectedProcedure.input(branchInsertSchema).mutation(...),其中输入 schema 直接来自@onlook/db。
客户端接入
apps/web/client/src/trpc/react.tsx 使用createTRPCReact<AppRouter>()建立类型安全的 API 客户端,并用单例模式(clientQueryClientSingleton)在浏览器侧复用 QueryClient;apps/web/client/src/trpc/query-client.ts 设置 30 秒默认staleTime避免 SSR 后立即重复请求,并通过 SuperJSON 完成数据脱水/水合。
Auth 与 Supabase
Onlook 使用 Supabase 管理认证与会话,文档规定了两类客户端并强调边界:
- 服务端客户端:apps/web/client/src/utils/supabase/server.ts 基于
@supabase/ssr的createServerClient,通过next/headers的cookies()读写会话 Cookie,供 Server Components、Server Actions 与路由使用; - 浏览器客户端:apps/web/client/src/utils/supabase/client/index.ts 基于
createBrowserClient,供客户端组件使用,并额外封装了存储桶的 URL 获取、文件信息与上传工具; - 禁止将服务端专属客户端传入客户端代码。
apps/web/client/src/utils/supabase/下还有admin.ts(service role 管理客户端)、middleware.ts(会话刷新)、request-server.ts等配套模块。
环境变量与配置治理
环境变量的定义与校验集中在 apps/web/client/src/env.ts,采用@t3-oss/env-nextjs+ Zod:
- server schema:校验
NODE_ENV、CSB_API_KEY、SUPABASE_DATABASE_URL、SUPABASE_SERVICE_ROLE_KEY、OPENROUTER_API_KEY等必填项,以及 Stripe、Bedrock、Vertex AI、Anthropic、OpenAI、n8n、Firecrawl、Exa、Langfuse、GitHub App 等一组可选密钥; - client schema:仅暴露
NEXT_PUBLIC_*前缀变量(如NEXT_PUBLIC_SITE_URL、NEXT_PUBLIC_SUPABASE_URL、NEXT_PUBLIC_SUPABASE_ANON_KEY),其中NEXT_PUBLIC_FEATURE_COLLABORATION用z.coerce.boolean()将字符串转为布尔; - 运行时映射:
runtimeEnv手动逐项映射process.env,这正是 t3-env 在 Edge runtime 下无法解构process.env的应对方案; - 跳过校验:
SKIP_ENV_VALIDATION环境变量可跳过校验(对 Docker 构建尤其有用); - 空串语义:
emptyStringAsUndefined: true,即SOME_VAR=''会被视为 undefined,从而触发 Zod 校验错误。
apps/web/client/next.config.ts 首部即import './src/env',在构建期强制执行环境变量校验——与文档要求完全对应。
process.env 的使用边界
文档对process.env的使用做了严格限定:
- 优先使用
@/env导出的env; - 在服务端专属 helper 中(例如 apps/web/client/src/trpc/helpers.ts 的
getBaseUrl()),只允许读取部署类变量VERCEL_URL/PORT; - 客户端代码中禁止使用
process.env; - 共享模块中如需读取,必须用
typeof window === 'undefined'守卫。
getBaseUrl()的实现正是这一规则的范本:浏览器环境返回window.location.origin,否则回退到https://${process.env.VERCEL_URL}或http://localhost:${process.env.PORT ?? 3000},并且整个文件只会被 tRPC 客户端链路在服务端/客户端两侧加载。
导入与路径别名
文档规定路径别名@/*与~/*均映射到apps/web/client/src/*。这在 apps/web/client/tsconfig.json 中得到确认:
"paths": { "@/*": ["./src/*"], "/*": ["./*"], "~/*": ["./src/*"] }配套规则:
- 不得将服务端专属模块导入客户端组件(会引发打包/运行时错误);
- 例外:编辑器某些代码编辑模块已使用 Node 的
path,仅在那些模块内复用,不要在客户端代码中导入process; - 必要时按环境拆分文件(server 文件 vs client 文件)。
从实际代码看,tRPC 层服务端文件普遍使用~/*(如root.ts中import { createCallerFactory, createTRPCRouter } from '~/server/api/trpc'),而客户端组件文件多用@/*(如layout.tsx中import { env } from '@/env'),两种别名等价可用。
MobX + React Stores
编辑器核心状态由 MobX 管理,apps/web/client/src/components/store/editor/engine.ts 中的EditorEngine是文档点名引用的示例 store(第 1 行即import { makeAutoObservable } from 'mobx')。文档针对 store 生命周期给出了一组易踩坑的纪律:
- 用
useState(() => new Store())创建 store 实例,保证跨渲染稳定; - 将活跃 store 保存在
useRef中;异步清理用setTimeout(() => storeRef.current?.clear(), 0),避免路由切换时的竞态(race conditions); - 避免用
useMemo创建 store 实例——React 可能丢弃被 memo 的值导致数据丢失; - 如果 store 实例放入 effect deps 会造成死循环,则拆分关注点(如 project 与 branch 分开);
observer组件仅限客户端;在功能入口放置一个客户端边界即可,子级 observer 无需重复use client(例:apps/web/client/src/app/project/[id]/_components/main.tsx)。
EditorEngine内部聚合了大量 Manager(BranchManager、CanvasManager、ChatManager、CodeManager、ElementsManager、FramesManager、StyleManager、TextEditingManager等),是理解编辑器架构的入口。
样式与 UI
- TailwindCSS 优先,全局样式已在 apps/web/client/src/app/layout.tsx 中导入(
@/styles/globals.css与@onlook/ui/globals.css); - 优先复用
@onlook/ui提供的组件与本地既有模式,避免自造轮子。packages/ui/src/components/下是 shadcn 风格的组件库(button、dialog、dropdown-menu、select、tooltip、accordion、command 等数十个组件),由 packages/ui/src/index.ts 统一导出; - 通过 layout 中的
ThemeProvider保持暗色主题默认(forcedTheme="dark")。
国际化(i18n)
- 使用
next-intl,Provider 位于 apps/web/client/src/app/layout.tsx(NextIntlClientProvider包裹全部子内容),并在 apps/web/client/next.config.ts 中通过createNextIntlPlugin接入; - 文案字符串存放在
apps/web/client/messages/*,目前包含en.json、es.json、ja.json、ko.json、zh.json五种语言及类型声明 en.d.json.ts; - 新增/修改文案应在 message 文件中进行,避免在代码中硬编码面向用户的文本;
- 保持 key 稳定,优先新增 key 而非破坏性重命名。
常见陷阱清单(Common Pitfalls)
文档将 Agent 最常触发的错误收敛为一张清单,每条都有明确的规避方式:
| 陷阱 | 后果 | 规避方式 |
|---|---|---|
需要事件/浏览器 API 处缺少use client | 事件未绑定 | 在功能根节点设置单个边界即可 |
新增 tRPC router 未在root.ts导出 | 端点不可达 | 在apps/web/client/src/server/api/root.ts注册 |
环境变量未在env.ts类型化/暴露 | 运行时/Edge 失败 | 优先env,避免客户端新增process.env读取 |
| 客户端组件导入服务端代码 | 打包/运行时错误 | 按环境拆分文件;path仅限既有代码编辑器模块 |
| 绕过 i18n 硬编码文案 | 无法多语言 | 使用 message 文件与 hooks |
用useMemo创建 MobX store | 引用丢失、数据丢失 | 用useState+useRef管理生命周期 |
| 路由切换时同步清理 store | 竞态条件 | 用setTimeout(..., 0)延迟异步清理 |
Agent 上下文纪律(Context Discipline)
为了在有限 token 下保持高效,Agent 应:
- 用 ripgrep 进行窄范围搜索,只打开需要的文件;
- 只读小段内容,避免读取
node_modules、.next、大体积资源文件; - 提出符合既有约定(conventions)的最小 diff,避免大面积重构(wide refactors)。
这与指南开篇"small, correct diffs, token-efficient"的目标形成闭环。
常用命令速查(Notes)
文档末尾给出的命令是仓库中 Agent 日常操作的基准,结合根 package.json 可得到完整对照:
| 命令 | 作用 | 根脚本对应 |
|---|---|---|
bun test | 运行单元测试 | bun --filter '*' test |
bun run typecheck | 类型检查(针对 web-client) | bun --filter @onlook/web-client typecheck |
bun run db:push | 将数据库变更应用到本地开发库 | cd packages/db && bun db:push |
bun run db:seed | 填充种子数据 | cd packages/db && bun db:seed |
bun run db:migrate | 执行迁移 | cd packages/db && bun db:migrate |
bun run lint | 全仓 lint | bun --filter '*' lint |
两条明确禁令需要特别注意:
- 禁止在自动化上下文中运行本地开发服务器(
bun dev类命令); - 禁止运行
bun run db:gen——该命令保留给维护者(maintainer),Agent 不应触碰数据库 schema 生成流程。
此外文档还提示:除非必要,不要使用额外类型("DO NOT use any type unless necessary"),以保持 diff 的克制与类型面的稳定。
总结:如何让 Agent 在 Onlook 仓库中安全高效地工作
综合全文,在 Onlook 仓库中工作的 Agent 应遵循一条主线:先理解架构边界(App Router 的 RSC/客户端划分、tRPC 的手动注册机制、Supabase 双客户端模型、env 的集中校验),再以最小 diff 落地改动。所有硬性规则都可以在源码中找到落点——root.ts是路由注册的强制检查点,env.ts是环境变量的唯一事实来源,layout.tsx是 Provider 与样式/主题/i18n 的装配现场,trpc.ts是过程(procedure)与权限语义的定义处,engine.ts则是 MobX store 生命周期的范本。遵循本文所梳理的约束与命令,Agent 既能保持代码风格与既有架构一致,也能避免最常见的运行时与边界错误。
【免费下载链接】onlookThe Cursor for Designers • An Open-Source AI-First Design tool • Visually build, style, and edit your React App with AI项目地址: https://gitcode.com/GitHub_Trending/on/onlook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考