按页面分配 JS 预算:Astro islands 的取舍
内容页 0 KB JS,交互只在需要处以 island 形式懒加载。主题切换用内联脚本就够,不必为一个按钮引入整个 React。
预算思维
展示层的原则是按页面分配 JS 预算:阅读类页面发 0 KB 组件 JS,炫技交互
(WebGL、图谱)只出现在首页和相册,且懒加载。Astro 天然适合这套思路——
默认不发任何组件 JS,只有加了 client:* 指令的 island 才会打包进浏览器。
别把轻交互做重
迁移时最容易犯的错,是把 Next 的「万物皆组件」思维带过来。比如主题切换:
Next 版本用了 next-themes + React context + useState/useEffect。但在
Astro 里,这就是一个按钮 + 一段内联脚本的事:
<script is:inline>
const stored = localStorage.getItem("theme");
const prefersDark = matchMedia("(prefers-color-scheme: dark)").matches;
if (stored === "dark" || (!stored && prefersDark))
document.documentElement.classList.add("dark");
</script>
放在 <head> 首帧执行,避免暗色模式闪烁(FOUC)。为一个按钮引入整个 React
运行时,是典型的预算浪费。
什么时候才用 island
判据很简单:这段交互离开 JS 还能用吗? 能,就用纯 CSS/内联脚本降级;
不能(如 3D 场景),才做成 React island 并 client:visible 懒加载。文字内容
永远写在 island 外面,WebGL 挂了页面也照样可读。
🔍 此前不知道自己不知道的
- ·Astro 默认不向浏览器发送任何组件 JS,`client:*` 指令是唯一的「JS 开关」
- ·主题切换这种轻交互用 `is:inline` 脚本即可,引入 next-themes/React 是过度设计
- ·无闪烁暗色模式的关键是把决定主题的脚本放在 `<head>` 首帧执行
❓ 下次值得追问
- ·R3F 首页场景用 `client:visible` 懒加载,能否保证 LCP 文本不被 WebGL 阻塞?
- ·islands 之间需要共享状态时,用 nanostores 还是干脆各自独立?