Lowdefy v6:有哪些新东西
Next.js 出局,Hono 和 Vite 进场。WebSocket、通知邮件、动态页面、定时端点、routine 里的 LLM 步骤,以及一个专为编码智能体打造的开发服务器。
Lowdefy v6 换掉了应用底层的框架。Next.js 被移除:服务端变成 Hono 应用,客户端是 Vite 打包出来的 React 产物,认证交给 Auth.js。你的 YAML 不用动。lowdefy dev、lowdefy build、lowdefy start 的行为和以前保持一致,迁移指南一屏就能看完。
换来的好处是:开发服务器改一次插件,大约 700 毫秒就能重载,而不用花 20 到 40 秒重新构建;生产服务器拷进容器,一条命令就能跑起来。这个版本的其他功能都建立在这个基础之上——实时通道、通知邮件、由服务端解析的页面、定时任务(cron)、把大语言模型(LLM)调用作为 routine 的一个步骤、把你的 API 暴露成 MCP 服务器,还有一整套让编码智能体(coding agent)看清自己造了什么的工具。
Hono 和 Vite 取代 Next.js
v5 的开发循环有个绕不过去的瓶颈:改一个插件,就得重建 Next.js 再重启服务器。到了 v6,开发服务器跑的是 Vite,Hono 应用以中间件形式挂载,插件改动直接走 Vite 的热模块替换(HMR),配置改动则通过服务端推送事件(SSE)触发重载。如果 package.json 没变,热启动会完全跳过安装依赖这一步,重启时间从 30 秒左右降到几秒。
页面跳转现在在客户端完成。首次加载时配置直接内嵌在 HTML 里;之后跳转只从 API 拉取下一页的配置,浏览器不做整页刷新,所以路由切换时页面状态和 WebSocket 连接都能保住。
生产构建会把一个完整可运行的服务器写进 .lowdefy/server:
lowdefy build
cd .lowdefy/server
node src/index.js
部署的就是这个目录。LOWDEFY_BUILD_OUTPUT_STANDALONE 已经删掉了,因为输出只有这一种。
破坏性改动不多。会话只失效一次,因为 cookie 前缀从 next-auth.* 改成了 authjs.*。NEXTAUTH_SECRET 变成了 AUTH_SECRET,如果配置了认证提供方却没设这个变量,构建会直接报配置错误。自定义的 next.config.js 不再生效,打包相关的定制要挪到 server 目录下的 vite.config.js。NEXT_PUBLIC_SENTRY_DSN 改成了 SENTRY_DSN,在运行时读取,所以轮换 DSN 不用再重新构建。auth 配置本身——包括 providers、adapters、callbacks 和受保护页面——都没变。
WebSockets
新增了一个顶层的 websockets: 用来定义频道(channel),页面通过 subscriptions: 订阅。为页面提供服务的 Lowdefy 服务器同时也负责推送消息,所有消息走一条多路复用连接,本地和 Vercel 上都一样。不用轮询,也不用单独跑一个 socket 服务。
websockets:
- id: ticker
type: Interval
properties:
ms: 1000
pages:
- id: dashboard
type: PageHeaderMenu
subscriptions:
- websocketId: ticker
blocks:
- id: ticks
type: Html
properties:
html:
_string.concat:
- 'Ticks: '
- _websocket: ticker.lastMessage.tick
频道类型都是插件。Channel 是发布/订阅的中继;Interval 按固定间隔发出信号,两者都在 @lowdefy/websockets-core 里。MongoDB 连接中的 MongoDBChangeStream 会把某个集合的变更事件推送给订阅的页面。频道的 properties 会在服务端按订阅逐一求值,所以用 _user 和 _payload 就能让频道变成针对具体用户的,而求值结果相同的订阅者会共用同一个运行时数据源。
页面在挂载时订阅,导航离开时取消订阅。可以用 onMessage、onSubscribe 和 onError 事件响应消息,也可以用 _websocket 操作符在任何地方读取频道状态。新增的 Publish、Subscribe 和 Unsubscribe 操作则覆盖了动态场景。频道访问权限沿用受保护接口的那套 auth 模式,在 auth.websockets 下配置 public、protected 和 roles。
客户端会自动按退避策略重连并重新订阅,所以一次部署或 serverless 函数跑满时间限制,对用户来说都是无感的。可以从 websockets 介绍开始看。
通知邮件
应用可以在配置里定义通知:用框架模板渲染出带品牌标识的邮件,再通过任意 SMTP 服务商发出。渲染由框架负责,存储通知记录和实际发送则留在你自己的 routine 里,所以任何数据库都能用它常规的请求类型接入。
notifications:
- id: task-assigned
type: NotificationEmail
properties:
subject: 'New task: {{ task.title }}'
title: Task assigned to you
message: |
Hi {{ contact.name }},
**{{ task.title }}** has been assigned to you.
metadata:
- label: Due
value: '{{ task.due_date }}'
button:
label: View task
模板属性是 Nunjucks 数据模板,插进去的值都是惰性的。哪怕任务标题写成 [click here](https://evil.example),渲染出来也是纯文本,绝不会变成链接。routine 里的 RenderNotification 步骤负责渲染单条数据,返回 { subject, title, preview, html, text, data },其中 { pageId, urlQuery } 链接会被展开成完整 URL。框架自带三个模板:NotificationEmail、DigestEmail 和 AlertEmail。自定义模板就是普通的 React Email 插件包。
品牌信息放在 app.email 下,默认值取自你已经配好的内容:companyName 来自应用的 name,primaryColor 来自 theme.antd.token.colorPrimary,logo 可以是 public/ 里的路径。新的 SMTP 连接封装了 nodemailer,兼容 SES、Postmark、Mailgun、Resend 或自建服务器。SMTP 和 SendGrid 都接受一个交付过滤器(delivery filter),方便把预发环境的邮件全部重定向到测试收件箱,两者也都会返回每封邮件的发送结果。
lowdefy emails 命令会构建应用,用它自己的 testData 渲染所有通知的预览,然后在 React Email 的预览服务器里打开。模块可以自带模板,作用域限定在模块入口,所以同一个用户管理模块装两次也不会冲突。
动态页面内容
请求负责改变页面上的数据,而新的 Dynamic block 改变的是 block 本身。把它指向一个 API 端点,页面加载时服务器会在进程内运行该端点的 routine,校验它返回的 block 配置,拼接进页面,再把结果发给客户端。客户端拿到的就是一个普通页面,照常渲染。
- id: insights
type: Dynamic
properties:
endpointId: resolve_insights
params:
area: insights
types:
blocks:
- Statistic
slots:
fallback:
blocks:
- id: insights_unavailable
type: Html
properties:
html: Insights are unavailable right now.
这个端点就是一个普通的 InternalApi 例程,接收 { params, pageId, blockId, urlQuery },返回 { blocks: [...] }。返回的 block 在送到客户端之前会先检查一遍:类型必须存在于客户端包里,属性要按 block schema 校验,Request 动作的引用必须在页面上确实存在。返回配置里的客户端操作符会多转义一个下划线——_state 变成 __state,_function 也是同样的规则。如果解析失败,fallback 插槽就会渲染,页面照常加载,除非你设置了 required: true。
仪表盘的各个区域跟随套餐或功能开关变化,表单根据数据库里的工作流定义动态生成,看板的分栏来自租户配置——这些以前都得写插件或者重新构建。现在,一个例程就够了。
定时端点
Api 和 InternalApi 端点可以声明 schedules,让它们按定时器运行:
id: purge-stale-conversations
type: Api
schedules:
- cron: '0 6 * * *'
payload: { mode: full }
- cron: '*/15 * * * *'
payload: { mode: incremental }
routine:
- id: purge
type: MongoDBDeleteMany
connectionId: conversations
properties:
filter: { updatedAt: { $lt: { _payload: cutoff } } }
构建时会校验 cron 语法,并写出 schedules.json 清单文件。部署到 Vercel 时,lowdefy vercel-output 每次部署都会根据它生成 crons 数组,不需要手动提交任何东西。定时任务以系统上下文访问 /api/cron/*,没有用户会话,靠 CRON_SECRET 保护,未设置时默认拒绝执行。
Vercel 只会在生产部署上触发定时任务,这意味着预发布环境的调度根本不会执行。config.cron.environments 让你一次性声明所有部署环境,生产环境会把每个环境的调度转发到该环境自己的定时路由。调度可以按环境分别设置,用一个 default 让其他环境继承,用 [] 关闭某个分支部署的定时任务。
还有三个控制项覆盖 Serverless 执行场景。端点上设置 async: true,会立即返回 { accepted: true },然后在后台运行例程。CallApi 步骤上设置 detached: true,会在独立的调用中运行目标,拥有全新的执行时长预算。webhook: true 把端点变成第三方 Webhook 接收器,拿到原始的 { body, query, headers },并原样返回响应体,调用方验证作为例程的第一步。
LLM 调用作为例程步骤
所有 AI 提供商的连接——Anthropic、OpenAI、Google 和 AI Gateway——都新增了两种请求类型:GenerateText 和 GenerateObject。它们是单次模型调用,可以作为例程步骤或页面请求使用,类型名称是共享的,所以切换提供商只需要改 connectionId。
routine:
- id: classify
type: GenerateObject
connectionId: claude
properties:
model: claude-haiku-4-5
prompt:
_payload: ticket_text
schema:
type: object
properties:
category: { type: string }
priority: { type: string, enum: [low, normal, urgent] }
GenerateObject 返回的数据会匹配你传入的 JSON Schema,这正是大多数例程决策需要的形状:给工单分类、提取字段、选择分支。
如果任务需要用工具,新增的 CallAgent 步骤可以在例程中运行你的某个智能体直到完成。没有聊天界面,也没有流式输出。智能体会循环调用工具直到结束,然后把 { text, finishReason, usage, toolCalls, toolResults } 存入 _step。结合定时任务,例程可以每天早上运行一个智能体,总结昨天的注册情况并标记异常,用的端点和聊天界面完全相同。
把 API 变成 MCP 服务器
根级 mcp 块列出要对外暴露成工具的 Api 端点,服务器则在 /api/mcp 上以可流式 HTTP 提供这些端点:
mcp:
name: my-app
version: '1.0.0'
title: My App
websiteUrl: https://example.com
icons:
- src: https://example.com/icon-512.png
mimeType: image/png
sizes: ['512x512']
endpoints:
- create-ticket
- search-customers
端点的 description 和 payloadSchema 会变成工具描述和输入 schema,所以想暴露的端点这两项都是必填的。工具调用每次请求都会用调用方的会话鉴权,并走和点击按钮相同的 _user 机制。
无法持有会话 Cookie 的客户端(比如 MCP 客户端或某个服务),则通过 auth.strategies 鉴权。apiKey 策略读取 X-API-Key 请求头,jwt 策略读取 bearer token,两者都会把该策略的角色授予调用方。现在未通过鉴权就调用有角色限制的端点,会直接返回 401,而不是被掩盖成其他错误。
任意厂商的文件存储
原来那组 S3 块被 @lowdefy/blocks-files 里与厂商无关的块取代:Upload、UploadPhoto、UploadDragger 和 Download。上传块按 id 调用上传策略请求,同时支持 POST 表单上传和 PUT body 上传,两者都有进度反馈。AwsS3Bucket 连接新增 endpoint 和 forcePathStyle,改这一行就能让 Cloudflare R2、MinIO、DigitalOcean Spaces、Backblaze B2 和 Wasabi 跑起来。新增的 GoogleCloudStorage 和 AzureBlobStorage 插件覆盖另外两家云,请求类型与之一致。
把 emitFileContent: true 打开,块会在浏览器里读取文件,并把 { name, size, type, content } 作为块的值发出,适合那些想通过例程加服务端写入请求来存文件的 app。Tiptap 编辑器和 AgentChat 的附件也走同一条流程。旧的 S3UploadButton 系列仍可用,但标记为弃用别名,lowdefy upgrade 里带一个 codemod 帮你重命名。详见托管文件。
事件动作里的控制流
事件动作列表现在接受 API 例程用的那套 :if、:switch 和 :return 控制:
events:
onClick:
- :if:
_not:
_state: form_valid
:then:
- id: warn
type: Message
params:
content: Fix the highlighted fields first.
- :return: null
- id: save
type: Request
params: save_record
以前想给一组动作加条件,得在每个动作上重复写一遍同样的 skip 表达式;想提前结束一个事件,就得抛一个 Throw,然后自己再 catch 接住。现在控制流可以嵌套,在 try 和 catch 列表里都能用。因为控制流原因没执行的动作会标记为 skipped,所以 _actions 查询结果和已有配置里的动作索引都不受影响。
为编码智能体打造的开发服务器
这一版里开发服务器的很多改动,是奔着「非人类开发者」去的。开发服务器始终会提供 /lowdefy-docs,这是一个 REST 和 MCP 端点,内容涵盖项目里安装的每一个 block、operator、action、connection 和 request 类型的 schema、示例和文档,本地插件也包含在内。智能体可以直接查到准确的接口约定,不用再猜属性名。
接下来是闭环的部分。lowdefy_build_status 返回当前的构建错误和警告(带文件和行号),以及近期浏览器运行时错误。lowdefy_screenshot_page 用无头 Chromium 渲染页面,返回 PNG 截图。lowdefy_inspect_state 读取你浏览器标签页里那个页面的实时 state、request 结果和事件日志——你可以自己点着复现 bug,再让智能体看到跟你眼里一模一样的东西。lowdefy_eval_operator 是一个 REPL,可以针对那份实时 state 运行 operator 表达式。lowdefy_snapshot_state 和 lowdefy_load_state 能把某个场景进行到一半的页面存成检查点再恢复,用 ?_checkpoint=<name> 这样的 URL 就能把当时状态的 App 直接丢给同事。总共有 23 个工具,都列在面向 AI 智能体的文档里。
npx lowdefy agent-setup
一条命令就能往项目里写入 .mcp.json、一份 Claude Code skill,以及 AGENTS.md 中的一节内容。在 monorepo(单仓库多项目)里,它写进仓库根目录;如果项目已有 CLAUDE.md,则追加到该文件末尾。lowdefy dev --mock-user '{"roles":["admin"]}' 启动开发服务器时就直接带登录态,这样无头(headless)渲染器就能给受保护的页面截图。每次 agent 工具调用还可以带上自己的 user,上一次调用以管理员身份渲染,下一次就以普通成员身份渲染。
另外两个功能是给人用的。按住 Option 或 Alt 点击运行中应用的任意元素,就能在 VS Code 里打开定义它的那份 YAML,并定位到具体行。按 Cmd+/ 或 Ctrl+/ 会弹出标注浮层:可以选中区块、在页面上画标记、写评论,再把一段反馈内容复制到剪贴板。每条标注都带着它的 blockId、所在的 YAML 文件和行号,以及一张带标注的截图。把它粘贴到你正在用的任意 agent 会话里就行。
同时运行多个应用
Cookie 是按主机名划分作用域,而不是按端口,所以 localhost 上的两个开发服务器以前会共用同一个登录态 Cookie 池,登录其中一个应用就会把另一个挤下线。现在开发服务器会根据应用的 slug 或名称生成一个 Cookie 前缀,每个应用各自拥有独立会话;同时 CLI 会自动挑选下一个可用端口,而不是在指定端口被占用时报错退出。如果确实想让两个应用共用一个会话,把两者的 auth.advanced.cookiePrefix 设成相同值即可。
部署
Docker。 服务器提供 GET /api/lowdefy-health 供容器探针调用;这个接口会跳过鉴权、日志和 Sentry,这样高频的健康检查就不会把你的日志刷满。收到 SIGTERM 信号时,它会主动断开 WebSocket 客户端、等待处理中的请求完成、刷新 Sentry 数据,并在 Docker 的宽限期内退出。新增的 lowdefy docker-output 命令会梳理服务器的运行时依赖图,只把它实际引用的文件复制到 .lowdefy/docker,这样构建工具链和仅客户端使用的区块包就不会被打进镜像。lowdefy init-docker 会生成一个 Dockerfile,把 CLI 版本固定为应用 lowdefy:version 所指定的版本,以非 root 的 node 用户运行,并加上 HEALTHCHECK。详见 Docker 指南。
Vercel。lowdefy init-vercel 会脚手架生成一套完整部署到 deploy/ 目录:静态资源放 CDN,其余所有逻辑用一个无服务器函数处理,通过 Build Output API 构建完成。在 config.vercel 里可以设置 maxDuration 和内存,新增的 config.requestTimeout 会限制每个请求的时长,默认 30 秒,这样即便上游调用卡住,也不会一直跑到平台在该计费方案下的时长上限。详见 Vercel 指南。
日志。每个请求都会输出一行标准访问日志,包含最终的 status 和 duration_ms。每行日志都带有 app_name、app_version 和 git_sha,这样在滚动部署期间,出错信息就能对应到某个构建,即便同时存在多个副本。请求 id 会遵循上游的 x-request-id 头,并在响应中原样返回。
更小的改动
-
_app操作符可以读取应用的slug、name、version、description、license、lowdefyVersion和gitSha,构建时和运行时的值完全一致。slug是新增的根字段,校验规则为 kebab-case;没有声明却引用它会导致构建失败,而不是把命名空间数据挂在null下。 -
TagSelector和TagMultipleSelector把选项渲染成可切换的胶囊按钮,每个值有固定的颜色。 -
日期选择器接受
presets,可以快速选择“最近 7 天”“本月至今”这类范围,基于_dayjs构建,每次渲染都会重新计算。 -
AG Grid 新增
menu单元格,把一行的操作收进一个触发按钮,而不是铺开一整列按钮。 -
Link动作支持replace和scroll,这样同页面链接如果只更新urlQuery,就不会跳到页面顶部,也不会多压入一条历史记录。 -
AgentChat欢迎屏支持tracks(带标签的起始提示词列,点击后填入输入框而不是直接发送)以及setInput方法。 -
state-refs构建检查从让生产构建失败改为给出警告。状态可以在运行时创建,所以这个检查只是启发式的,误报导致部署失败比漏掉更糟。 -
模块延迟加载统一到一个构建级别的注册表。对于相互内嵌的模块,入口顺序不再有影响,循环错误的提示会列出实际的值链。
-
Google Sheets 连接迁移到当前的
google-spreadsheet库,同时修复了在 Node.js 26 上的崩溃问题。
升级
npx lowdefy upgrade
升级命令会自动识别你的版本,并针对该版本到 v6 之间的变更生成 codemod 提示。由于配置 schema 没有变化,大多数应用只需要重命名环境变量,再登录一次即可。完整的破坏性变更清单见 v5 到 v6 迁移指南。
Lowdefy 联合创始人
Gerrie 擅长设计可扩展的软件方案,专攻复杂业务流程的简化。他与他人共同创立 Lowdefy,希望让人人都能轻松搭建 Web 应用。
更多文章
用 YAML 写 Wordle(外加 22 行 JavaScript)
除了上色,其余全由 YAML 完成。上色交给 JavaScript,只用了 22 行。
Lowdefy v5.3:30 行 YAML 跑起 AI 智能体
现在,Lowdefy 应用可以运行 AI 智能体,把你现有的接口当作工具来调用。