上周我把自己博客的阅读量统计重写了一遍。
起因很蠢。我之前用了一个第三方的统计脚本,挂在每个页面的 head 里,结果它在某些地区加载要等两秒多,整个页面都卡住。用户还没看到字,先等一个进度条。我说这不行。
Astro 的卖点大家都听过,零 JS 默认。但我真正用起来,是在三个地方。
第一个是主题切换。
我博客有暗色和亮色两套。最开始我把切换逻辑写在一个全局脚本里,打包出来大概 3.4 KB。后来我想,这不就是个 island 吗,干脆写成一个组件,
---
// ThemeToggle.astro
const theme = Astro.cookies.get('theme')?.value ?? 'dark';
---
<button id="theme-toggle" data-theme={theme}>
切换
</button>
<script>
const btn = document.getElementById('theme-toggle');
btn?.addEventListener('click', () => {
const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
document.documentElement.dataset.theme = next;
document.cookie = `theme=${next}; path=/; max-age=31536000`;
});
</script>
就这么点代码,Astro 默认把它当成 island 处理,hydration 策略是 load 时。页面其它部分还是纯 HTML。
第二个是阅读量。
我不想接第三方了,太慢。我自己写了个小接口,每次打开文章就发一个请求过去。这个必须得有 JS,没法静态化。所以它是个纯粹的 client island,
---
// ViewCounter.astro
const { slug } = Astro.props;
---
<span id={`vc-${slug}`}>…</span>
<script define:vars={{ slug }}>
fetch(`/api/views/${slug}`)
.then(r => r.json())
.then(d => { document.getElementById(`vc-${slug}`).textContent = d.count; });
</script>
这里我踩过一个坑。一开始我没加 define,直接把 slug 拼进字符串里,结果拿到的是 undefined。我盯着控制台看了十分钟,才反应过来 Astro 的前端脚本和后端 props 不互通。这个设计其实挺合理,但我当时就是没往那儿想。
第三个是搜索框。
我用了页面内的一个简单 filter,输入关键词过滤文章列表。这块我也没用现成的库,自己写了二十来行。它只在 /blog 这个页面挂载,其它页面根本不加载它。
说实话,我到现在也没想清楚的一个问题是,island 多了之后,怎么衡量到底省了多少 JS。我看过有人说 Astro 站点平均比 Next 轻 90%,但我自己没真正测过,只是凭感觉。可能哪天我该认真量一下。
如果你也在用 Astro,不妨看看你页面上哪些东西其实不需要 JS。我基本就是这么一路试过来的。
种下你的想法
在花园里留下一条评论,和这篇文章一起生长。