静态博客也需要一点交互:搜索框、标签筛选、主题切换、折叠面板。关键在于——不要因为一点点交互,就让整个页面都变成客户端应用。
Svelte 5 在这个项目里的角色,是「局部增强」,而不是「接管页面」。
先静态,再交互
大部分文章页、归档页、关于页都是纯 Astro 组件,构建时直接输出 HTML。只有真正需要响应点击和输入的部分,才会写成 Svelte 组件,并通过 hydration 指令挂载。
适合首屏就需要立即可用的组件,例如导航栏里的搜索入口。对应的指令是 client:load,代价是脚本会在页面加载后马上执行。
适合首屏之外、滚动到附近才需要工作的组件。对应的指令是 client:visible,文章底部的相关推荐、相册列表都可以用这个策略。
完全跳过服务端渲染。对应的指令是 client:only,只适合依赖浏览器 API、无法在构建时渲染的组件。
这三种策略的区别,本质上是在回答一个问题:这个交互最早什么时候可能被用到?
Runes 让状态更直白
Svelte 5 的 runes 写法比过去的响应式语法更接近普通 JavaScript:
<script lang="ts"> let query = $state(""); let selectedTag = $state(""); let filtered = $derived( items.filter((item) => item.title.includes(query)) );</script>
<input bind:value={query} />$state 声明可变状态,$derived 声明派生值。比起手动维护依赖关系,这种写法更容易读,也更不容易漏掉更新。
一个真实的取舍
筛选器需要读取 URL 参数,让刷新和分享后的状态保持一致。这段逻辑只能在浏览器里运行,所以放在 onMount 中;但它又不需要整个页面都变成客户端组件。
交互的边界应该由需求决定,而不是由框架决定。
我把筛选器拆成独立组件,只让它自己承担状态,页面其余部分继续静态输出。这样一来:
- 没有筛选需求的读者,不会为这段脚本付出首屏成本;
- 需要交互的读者,点开筛选器后功能完整可用;
- 构建时仍然能生成可读的 HTML,搜索引擎和 RSS 不受影响。
那动画呢?
筛选切换时的淡入和加载指示器也用 Svelte 状态驱动,但动画本身遵循系统的 prefers-reduced-motion 设置。交互可以有趣,但不应该强迫所有人接受。
小结
Svelte 5 在这个博客里不是主角,而是配角。它负责把少数需要响应的地方做得顺滑,其余部分交给 Astro 在构建时完成。这种分工听起来保守,但对内容站来说,保守往往意味着更快的首屏和更少的故障面。
本站技术手记
连载中分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 12 天
部分内容可能已过时