外观
品牌首页 apps/home
natlan.io 裸域上的静态首页,生产部署 https://natlan.io/。
它不是框架应用:一份手写的自包含 index.html、一个模块 Worker、一个 Svelte 岛, 外加一个把网络数据烘焙进 HTML 的构建期脚本。艺术方向见 brand-identity.md。
1. 组成
| 文件 | 是什么 |
|---|---|
index.html | 整个页面,自包含:内联 CSS + 一段内联脚本。设计令牌与共享层同源,另有台地灰阶 |
index.js | 模块 Worker,负责路由与响应头 |
fleet-widget/ | Svelte 岛的源码,构建产出 fleet.bundle.js |
fleet.bundle.js | 岛的构建产物(已提交,Worker 作为文本模块打包) |
bake.js | 把网络数据快照烘焙进 index.html |
world-land.geojson | 岛在运行时取的陆地轮廓(apps/control/static/ 那份的副本) |
canyonlands.jpg | hero 出处弹层的照片(au_ears / Flickr,CC BY-SA 2.0,弹层内已标注) |
favicon.svg | 与控制台的 src/lib/assets/favicon.svg 保持一致 |
Green_River_Overlook_Ekker_Butte.jpg | 地形取景参考原图 |
2. Worker 路由
index.js 只处理 GET / HEAD,其余返回 405。
| 路径 | 响应 | Cache-Control |
|---|---|---|
/ | index.html | public, max-age=300 |
/favicon.svg | favicon | max-age=86400 |
/fleet.js | fleet.bundle.js | max-age=300 |
/world-land.geojson | 陆地轮廓 | max-age=86400 |
/canyonlands.jpg | hero 照片 | max-age=2592000 |
/robots.txt | 生成 | — |
/sitemap.xml | 生成(单页一条 <url>) | max-age=3600 |
/nodes/* | 302 → 控制台 | — |
| 其他 | 302 → / | — |
改动页面内容时记得把
SITEMAP里的<lastmod>往前推。
3. 三层数据,逐层降级
首页的机群地图与统计条带有三层来源,任何一层失效都不会让页面坏掉:
| 层 | 来源 | 何时生效 |
|---|---|---|
| ① 烘焙快照 | bake.js 写进 index.html | 始终存在。无 JS 也完整成立 |
| ② 内联脚本刷新 | GET /control/v1/public/map | 页面加载后。fetch 失败就保留快照 |
| ③ FleetMap 岛 | GET /control/v1/public/fleet | 岛脚本挂载成功后,覆盖静态回退 |
这是首页唯一的 JS 依赖策略,也是它能做成纯静态的原因。
岛在挂载前会先把 fallback 区块的高度冻结到容器上,避免换入时下方内容跳动 (2026-07 的 CWV 报告里这一段 CLS 0.296)。
4. FleetMap 岛
fleet-widget/ 挂载的是 $ui 里的 FleetMap.svelte——与控制台渲染的是同一个 组件,因此首页的地图不可能与控制台漂移。
两个值得记住的细节:
- 岛的入口直接引
$ui/host.svelte而不走桶文件。桶里 re-export 了CapCheck, 它的?url资源(Cap 的 WASM 与 pako)会被一并 emit 到产物目录——JS 侧摇得掉, 文件却留下了。首页用不到验证码。 - 注入的宿主是两个常量(
locale: en、theme: dark)而不是响应式实例:首页 没有语言切换器,明暗全交给 CSS 的prefers-color-scheme。theme.mode只被图表 当重绘信号,首页只挂地图,取值不影响渲染。
合仓前这个岛要靠四个 shim 桩掉控制台的 $lib/api、toast、urlstate、 $app/environment,还得写 CSS 把用不到的按钮藏起来。共享库的可选 prop 契约把这些 全消掉了,产物小了 47%。
5. hero 地形
hero 背景的等高线是真实地形:从 AWS terrain-tiles DEM 烘焙的 Canyonlands Island in the Sky 一带高程(hypsometric 填充 + index contours),标出了 Green River Overlook——ⓘ 徽标里那张 CC BY-SA 照片正是在那里拍的。
重新生成:node fleet-widget/scripts/terrain.mjs,取景由脚本里的 ANCHOR_FX/FY 控制。
6. 部署
Cloudflare Git 关联 Worker(natlan-io-home):
| 项 | 值 |
|---|---|
| Build command | npm run build:home(重建 fleet.bundle.js) |
| Deploy command | npx wrangler deploy -c apps/home/wrangler.jsonc |
| Build watch paths | apps/home/* + ui/* |
wrangler.jsonc 里的 rules 把 html / svg / geojson / *.bundle.js 当文本模块、 jpg 当二进制数据模块打包进 Worker;workers_dev: false 关掉第二入口。
裸域自定义域经 PUT /accounts/{account}/workers/domains 绑定,跨部署保持。
⚠️
bake.js不在构建流程里。 Git 关联不会替你跑它。烘焙快照过期时要手工node apps/home/bake.js并提交。见 guides/refresh-home-snapshot.md。
手工部署仍然可用:npx wrangler deploy -c apps/home/wrangler.jsonc。