Web Device:声明就是网站,两条路怎么选

AFS(Agentic File System)是 ARC 里管数据、应用、配置的那套统一目录结构。在 ARC 里,"device"就是把 AFS 目录下的东西对外变成一种界面的方式,而不巧的是,通向网页的两条路都叫"device",很容易被当成一回事,包括我自己有时候也会说混。一条是 /dev/ui/web:把浏览器当成一种可以实时操作的设备,AUP(Agentic UI Protocol,ARC 里描述界面长什么样的声明式协议)在浏览器里现场渲染,你点一下、拖一下,它立刻响应,这是 UI Device。另一条是 Web Device:只要在 AFS 的任何一个目录下放一个 .web/ 声明,这个目录里的数据和 AUP 就会在服务端被渲染成一个网站,header、footer、导航、SEO 这些全站层面的东西全部自动有。
两者不是谁比谁高级,更像终端和打印机的关系:UI Device 是终端,你敲一下它就动一下;Web Device 是打印机,你把 .web/ 和 AUP 喂给它,它吐出一整份网页,喂一次吐一次,不会自己一直转。这篇想把这两条路彻底讲清楚:它从哪来、解决了什么问题、具体怎么声明、声明完能白拿到什么、以及两条路各自该在什么场景用。
起源:声明先于实时交互
这套设计不是先有理论再落地的。起点是一个很具体的麻烦:团队手上有好几个域名要维护,流程是老派静态站点生成器那一套,人写 HTML/Markdown,跑一遍构建脚本,再部署上去。内容一变就要重新构建、重新部署,不是声明式的。每加一个新域名,这一整套流程就要再走一遍,而且各个站点之间没有任何共享,各自维护各自的 header、footer、SEO 配置,改一次品牌色要挨个改一遍。
Declaration first; path selection follows.
2026 年 2 月,这背后的判断很直接:AFS 已经有 AUP,能把任何目录渲染成 UI。网站为什么不能也是 AFS 的一种设备?想清楚这一点,Web Device 的雏形就定了,而且从一开始就定了它和 UI Device 的分野:渲染发生在哪,浏览器里还是服务端,决定了各自能做什么。UI Device 能维持一个实时会话,Web Device 不能;但换来的是全站层面不用碰代码,AUP 想渲染成什么样子,它就渲染成什么样子,视觉表现力不打折。
有什么用:数据是事实源,声明就是网站
这套东西真正想解决的问题,不是"怎么让网页动态渲染",是反过来的:怎么让 AFS 里本来就存在的数据,不需要人再手写一遍网页。团队做的事、产品的状态、每一篇文章,本来就以某种结构存在于 AFS 里;没有 Web Device 之前,想把这些数据变成一个网站,还是得有人另外写 HTML、套模板、接内容管理系统,这是重复劳动,也是漂移的根源,数据改了,网页忘了同步,两边就开始说不一样的话。
Web Device 把这套流程砍掉了。AFS 里的数据是事实源,声明一份 .web/,网站会跟着数据变化自动重新渲染,不需要人工手动去把网页和数据对齐。我们内部有条判断叫"Everything is Context":AFS 里的东西才是唯一的事实,网站、文档、界面都只是这份事实的呈现方式,不该另外单独维护一份。Web Device 就是这条判断落到"网站"这一种呈现上的具体做法。
一个新域名要上线,不用再从零搭一套构建部署流水线,声明 .web/ 里那几个文件就有了完整的站;以前分域名各维护一份的公共部分,现在是共享的声明,改一处,所有站点跟着变。渲染这条流水线上还顺带做了几件事,后面会展开。
如何用:.web 目录长什么样
Web Device 靠两层声明拼出一个网站。.web/ 管全站层面的东西:header、footer、导航、SEO 模板这些,声明完就有了,不用碰代码。单个页面的内容不在 .web/ 里,另放在 pages/<route>/ 下,用 AUP 写,是语法不是传统渲染代码,但确实要学。
这篇文章所在的网站自己就是个活案例。想看实际长什么样,arcblock-site 仓库里 blocklets/arcblock/.web/ 目录长这样,感兴趣可以对照着看:
.web/
├── domain # arcblock.io
├── tone # editorial
├── palette # neutral
├── color-scheme # light
├── render-mode # static
├── locale / locales # 默认语言、支持哪些语言
├── tokens.json # design token
├── seo/
│ ├── title-template # "%s | ArcBlock"
│ ├── description
│ └── og-site-name
├── template/
│ ├── header-actions
│ ├── footer
│ ├── footer-links
│ ├── navigation
│ ├── docs-actions
│ └── logo.svg
├── components/
│ └── site-footer/ # manifest.json + render.js + style.css,自定义组件
├── redirects / legacy-redirects.csv # 跳转表
└── tag-slugs # 标签显示名到 slug 的映射表这些文件分两种。大多数是一行配置:domain 是一行 arcblock.io,tone 是一行 editorial,render-mode 是一行 static。少数文件是表,比如 tag-slugs、redirects,一行一条记录,也是声明,不是渲染逻辑。真正会碰到代码的,只有 components/:内置组件够用就不用管它,想要一个内置组件没有的行为,才需要在 components/<name>/ 下自己写一段 render.js。这篇文章所在网站的 site-footer 就是这么来的,多写了一小段处理本地开发时域名跳转的逻辑。
单个页面的内容不放在 .web/ 里,放在 pages/<route>/ 下:一个 layout.aup 写页面结构,一个 seo/ 放这个页面的 SEO title 和 description。layout.aup 用 AUP 的声明式语法写,一个字段配一种语言的文案。举个最简单的例子,一个页面大致是这样:
page index {
i18n {
title from "home.title" { en "ArcBlock" zh "ArcBlock" }
}
}声明完不是直接就上线了。Web Device 拿到 .web/ 的全局声明和每个页面的声明,先渲染到一份预览区域,确认没问题再切换成正式版本;改动一个字段,Web Device 只重新渲染受这个改动影响的页面,不是每次都把全站重渲染一遍。中间没有模板引擎,也不需要人盯着构建流水线。
能有什么效果:这几件事是白送的
声明完 .web/ 和几个页面,能拿到的不只是一个能打开的网站。渲染这条流水线上还挂着三件事,是这套系统自己做的,不需要网站作者额外配置:
每次渲染都顺带做一遍 SEO 体检。 标题长度合不合适、有没有写一段简介给搜索引擎看、图片有没有配文字说明(方便屏幕阅读器和搜索引擎理解图片内容)、转发到微信朋友圈或者 X 时显示的标题和封面图配好了没有,这些以前要么靠人肉检查、要么根本没人管的细节,都会被检查一遍,缺一项就知道缺一项,不用等上线之后被搜索引擎降权或者转发出去发现封面图是空的才想起来。
内部链接会被自动核实。 页面之间互相链接,链到的路径存不存在,渲染进预览的时候就顺手查了。手写网站最容易出现的"死链接",在这条流水线里是上线前就能发现的问题,不是上线之后靠人工巡查才能抓到的。
访客有基础的行为记录,而且能大致分清楚是谁在访问。 停留时长、滚动深度、进来的路径,这些常规数据会被收集;更实用的是它会根据访问时留下的线索,把访客粗分成几类:真人、搜索引擎的爬虫、AI agent 的爬虫、社交平台转发时的抓取机器人,剩下认不出来的归进一个"其他"桶。不是每一条都保证判得准,但足够看出一个大致比例。在越来越多流量其实是 AI 在读你网站的现在,这个区分本身就是有意义的信息,而且是声明 .web/ 就自带的,不用另外接第三方分析工具。
这三件事不是规划中的功能,是渲染管线里已经在跑的代码,声明 .web/ 的时候就默认打开。
两条路,怎么选
两种情况,Web Device 不是答案,该走 /dev/ui/web。
第一种,页面需要维持一个"活的"状态:填表单的时候边打字边校验格式对不对,拖一下就重新排序,好几个人同时改一份文档还能看到彼此的光标。这些都要一个一直连着的会话,Web Device 不维持会话。
第二种,操作页面的不是人,是 agent。比如一个 agent 要替用户走完一套多步骤的设置流程,得能一步步点下去、跳到下一屏。UI Device 有一条事件通道,agent 能像人一样点击、跳转、驱动交互;Web Device 吐出来的只是一段 HTML 字符串,没有这条通道,agent 只能读,没法操作。
还有一种更极端的:要做的是重度依赖浏览器专属能力的东西,比如实时音视频、复杂动画引擎,这两条路都不是答案,那是另一个话题了。
除了这几个场景,两条路在原理和能力上的差异,摆成一张表更直观:
| 维度 | UI Device(/dev/ui/web) | Web Device(.web/) |
|---|---|---|
| 交互 | 实时:点击、拖拽、表单校验都当场响应 | 无:打开看到的是渲染好的一份网页,点链接才会有新的一次请求 |
| 渲染时机 | 用户连接、操作的那一刻,持续渲染 | 数据变化时或有人请求页面时渲染一次,渲染好的网页可以一直被访问,直到下一次数据变化 |
| SEO | 不是重点,内容在会话里,搜索引擎抓不到 | 重点在这,检查是自带的 |
| 输出 | 一份可以被操作的实时界面,靠一条常开的连接同步状态 | 一份写死的 HTML 网页,打开链接就拿到,不需要额外连接 |
| 典型场景 | 表单、拖拽排序、协作编辑、agent 驱动操作 | 内容页、文章、产品页、官网首页 |
"渲染时机"这一行是最容易搞混的一点:Web Device 不是一直开着等你访问,而是像打印机吐出一张纸,纸吐出来之后就放在那儿,谁来看都是同一张,直到下一次数据变了、重新吐一张新的替换掉。
两条路,不是一条
回到开头:/dev/ui/web 和 Web Device 不是谁取代谁的关系。一个为了让人和 agent 能实时操作;一个为了让 AFS 里那些本来就是事实的东西,声明出来就变成一个专业的网站,SEO 体检、死链检查、访客分析这些以前要专门配置的事,跟着渲染顺带就做了。想看实际长什么样,arcblock-site 仓库的 .web/ 目录当场就能看,这一层照着放文件就能复用;单个页面的结构和文案,还是要用 AUP 重新写一遍。
两者都叫 device,会混淆不是巧合,是因为它们本来就是从同一个地方长出来的:都是同一套 AUP 语义,只是一个交给浏览器实时处理,一个交给服务端印出来发布。分清楚这一点,剩下的就是照着目录结构把文件放对位置,选对那条路。
本页涉及
产品
-
ARC
active
Blocklet 的运行时。它给开发者一个地方来运行以 Blocklet 描述的应用,连同那个 Blocklet 声明自己需要的资源。
术语
-
AFS
AFS(Agentic File System)把与任务有关的文件、服务和正在进行的工作组织成可查看的资源视图。它不是把一整台机器或一堆 API 交给 agent,而是给任务一块有名字、有边界的工作范围。
-
ARC
ARC 是 Blocklet 的运行时。它让开发者把应用、运行位置和所需资源分别说清楚:Blocklet 描述应用,ARC 运行它,AFS 表达它要接触的资源。
-
AUP
AUP(Agentic UI Protocol)先表达应用要呈现什么、可以做什么;运行时再根据设备能力呈现合适的部分。重点不是复刻同一张屏幕,而是让同一项应用保留自己的内容和动作。
-
Device
在 ARC 里,device 就是把 AFS 目录下的东西对外变成一种界面的方式。