基于真实 DOM 还原 Skeleton 的工程思考

在前端开发中,Skeleton(骨架屏)是提升用户感知性能的重要手段。常见做法是单独写一套灰色占位组件,但这种方法存在一些明显的问题: 骨架屏和真实 UI 结构脱节,修改 UI 需要额外同步 skeleton; 文字行高和布局难以精准还原,容易出现错位; 页面渲染过程中容易出现布局抖动(CLS)。 本文分享我在组件库中对骨架屏的思考,以及如何通过与组件耦合的方式实现精准还原。 --- 传统 Skeleton 的问题 常见的实现方式是页面层写一套假的 Skeleton,例如: 这种方式存在几个问题: 结构脱节\ \ 骨架屏和真实组件是两个平行的版本,修改组件必须同步改 skeleton。 布局不精确\ \ 文字、行高、padding、响应式高度等都可能不一致,导致骨架屏和实际内容错位。 维护成本高\ \ 组件库或页面升级时,需要额外维护 skeleton 组件。 --- 我的方法:Skeleton 与组件结构耦合 我的核心思路是: 骨架屏应该是组件的一种状态,而不是独立组件。

Next.js 中如何优雅地返回列表页,并保留之前的分页状态

做博客列表页时,有一个很常见、但很容易被忽略的体验细节: 用户从 点进某篇文章后,再点击“返回”,应该尽量回到刚才那一页,而不是一律回到 。 这个需求看起来不复杂,但如果处理得不仔细,最后体验通常会变成下面这样: 列表页本身支持分页和筛选 进入详情页后 URL 很干净 但返回时丢失了 和 用户被送回第一页,只能重新翻到刚才的位置 最近我正好在自己的站点里处理了这个问题,顺手把思路整理一下。 一、先把列表状态放进 URL 第一步其实很关键: 如果列表页当前页码、筛选标签这些状态不在 URL 里,那“返回到之前的位置”基本就无从谈起。 比如博客列表页最好长这样: 这样至少有两个好处: 刷新页面后状态不会丢 你可以明确知道“用户当前看到的是哪一页、哪一个筛选条件” 我这边是用 [](https://nuqs.dev/) 管理查询参数的,代码大概像这样: 这里的重点不是一定要用 ,而是: 页码、筛选条件这类列表状态应该是“可序列化”的 它们最好能直接映射到 URL 查询参数 这样列表页本身就先站稳了。

#Next.js#Frontend#Routing

把 Better Auth 跑在 Cloudflare Workers + D1 上踩了哪些坑

最近在给自己的站点接入用户系统,技术栈是 Cloudflare Workers(Hono)+ D1 + Better Auth。本以为照着文档走一遍就行,结果一路踩了三个连环坑,每个坑都有独立的报错,记录一下排查过程。 --- 坑一:注册/登录线上 503,本地完全正常 这是最难受的一个坑,因为本地开发一切正常,推上线就 503,让人摸不着头脑。 报错(wrangler tail) 原因:scrypt 是 CPU 杀手 Better Auth 默认用 scrypt 做密码哈希。scrypt 由 Colin Percival 在 2009 年设计,核心目标就是让暴力破解在计算资源上代价极高——它同时消耗大量 CPU 和内存,即便是现代 GPU 也很难并行加速。 这个特性在传统服务器上是优点,但在 Cloudflare Workers 上就成了致命问题: Workers 免费套餐单次请求 CPU 时限只有 10ms(付费套餐也只有 30ms) scrypt 在 Node.

从 "组件外也能改 Zustand 状态" 聊到它的底层原理

这次对 Zustand 的理解升级,其实是从一个很简单的问题开始的: 为什么我可以在组件外面通过 修改状态,而且组件里的值还能自动更新? 一开始我一直以为 Zustand 是基于 React Context 实现的。既然是基于 Context,那它应该只能在组件树里用,离开组件就没法工作。但实际测试下来发现: 在组件外调用 完全没问题 组件内通过 订阅的值也会自动更新 根本不需要 Provider(在默认用法下) 这让我意识到,我对 Zustand 的理解是错的。 一、Zustand 不是基于 React Context 很多人(包括我)会自然地把“全局状态管理”跟 React Context 绑定在一起。 但 Zustand 的默认实现,其实完全不依赖 React Context。 如果你这样创建一个 store: 这个 做的事情本质上是: 在内存里创建一个 store 对象 这个对象内部维护: 当前 state 一组 listeners(订阅者) getState setState subscribe

#Frontend#Zustand

从 shadcn/ui Button 到可定制 loading 的业务按钮

在前端开发中,按钮是最常用的组件之一。shadcn/ui 提供了默认的 Button 组件,但在实际业务中,它存在一些不足: 默认按钮没有 loading 状态 无法阻止重复点击 loader 样式无法自定义,不适应不同主题 为了解决这些问题,我在 shadcn/ui Button 的基础上,封装了一个业务 Button 组件。 组件设计 核心目标: 增加 loading 状态 当按钮正在处理请求时显示 loader 自动禁用按钮,防止重复提交 支持 loader 样式自定义 通过 可以修改 loader 样式,例如深色模式下反色 继承原生 Button 属性 保留所有原生属性,如 , , 安全点击逻辑 当按钮处于 loading 或 disabled 时,阻止点击事件 核心代码 使用示例 为 true 时显示 loader 按钮禁用,防止重复点击 loader 样式可通过 自定义 总结 这个业务 Button 组件解决了 shadcn/ui 默认 Button 的不足: 增加 loading 状态 loader 可自定义样式 自动禁用点击,