工作室日志
深入探讨前端工程、极简设计以及高端数字体验背后的技术工艺。
I write my date pickers by hand — no react-day-picker, no date-fns, no dayjs. Radix supplies popover positioning and focus management; the 42-cell month, the week offset and the month arithmetic are about forty lines of pure functions. That held fine for one grid. The moment I rendered two months side by side, the arrow keys started teleporting. Each grid draws six full weeks, so January's trailing cells and February's leading cells are the same fourteen days, and data-date existed twice in the DOM. querySelector returns the first match, so focus kept landing in the wrong grid. The fix is to render the adjacent-month days as inert blanks — but only when a second grid is on screen.
Next renders <link rel="alternate" hrefLang> through React DOM, which writes the prop name verbatim, in camelCase — so grep hreflang on the served HTML returns zero and you conclude the tags were never emitted. They were. The two failures that actually cost indexation are quieter: canonicalizing every locale to /en, which discards the cluster, and a route-level alternates object, which replaces the layout's rather than merging into it. All three are catchable with one pass over the build output, which is the only artifact that tells the truth.
pnpm 11 finished moving its supply-chain settings into pnpm-workspace.yaml — that is now the only place they are read from — and three of them accept a shape that looks right and does nothing. allowBuilds is a map, not a list — write it as a YAML array and pnpm turns your entry into a package literally named '0'. trustPolicyIgnoreAfter counts minutes, so 365 means six hours, not a year, and quietly turns trustPolicy: no-downgrade into a no-op. And pnpm 11 no longer reads the pnpm field of package.json at all — it warns once and exits 0. None of the four fixes a CVE in a version you pinned. That is a different problem with a different fix.
A script rmSync'd a source file I had never committed — no git blob, no recycle bin, no editor backup. Most .js.map files Next.js writes into .next/ carry a sourcesContent array holding the original, pre-transform text of each module they compiled, so a project that has been built once since the file existed still has that file on disk, inside the build output. I got 660 lines back byte-for-byte. The lesson underneath is the one that comes first: back up every file a destructive script can touch, not just the ones git can restore.
shadcn 4.x generates components on top of Base UI instead of Radix. asChild is gone, and Accordion no longer accepts type or collapsible — both are TS2322, so tsc hands them to you in the first run. The one that survives a green build is Select: Base UI's <Select.Value> renders the value, not the selected item's children, unless you pass an items map to <Select.Root>. No error, no warning, and the open popup still looks correct — only the closed trigger reads buy where it should read Buy a template.
next/font/google 会在构建时从 fonts.gstatic.com 下载 woff2 文件。当 Google 轮换了某个文件的哈希之后,我的部署开始因为 404 而失败,而我在本地根本复现不出来——因为同一个请求在早几秒的时候已经在我机器上成功过了。修复方式:把字体文件放进仓库,改用 next/font/local,让这些字节成为你自己掌握的输入。
对 filter 和 backdrop-filter 来说,Lightning CSS 会把带前缀和不带前缀的两条声明合并成一条,只保留你最后写的那条的前缀标记。所以 backdrop-filter 后面再跟一条 -webkit-backdrop-filter,编译出来就只剩 WebKit 版本。Chrome 根本不支持 -webkit-backdrop-filter,于是模糊效果在生产环境里悄无声息地消失了,而源码看上去完全正常。大多数其他带前缀的属性经过同样的处理都毫发无损——这正是这一个容易被漏掉的原因。
在 Tailwind v4 里,@theme 中的 --z-overlay: 200 并不会生成 z-overlay 这个工具类。工具类是从 namespace 读取的,而 z- 对应的那个是 --z-index-。 不会报错,也不会有警告。这个 class 就是不存在,元素退回到 z-index: auto,任何带有真实正数 z-index 的东西都会盖在它上面 —— 在某些路由上会,在另一些路由上不会。 真正能一锤定音的检查,是在构建产物的 CSS 里 grep 这个 class 名,而不是在源码里。
如果你的首次渲染依赖 document、matchMedia、location.hash、localStorage 或者某个 cookie,那你离一次 hydration mismatch 就只差一个不一样的值 —— 这些 API 在 SSR 期间根本不存在,而在客户端的首次渲染时就已经是真实可用的了。 tsc、ESLint 和跑在 Node 环境里的单元测试照样全部通过,因为它们谁都不会真的做一次 hydration 渲染。 两个诚实的修法:挂载后再 seed(简单,会闪一帧),或者在服务端把值读出来(不闪,但需要一次 cookie 往返)。
params 是一个 Promise;metadata 是按字段合并的,不是深度合并;PageProps/LayoutProps 只有在有东西生成它们之后才存在;pre-paint 的主题脚本必须由服务端渲染。 四个里有三个是静默失败:build 绿的,类型绿的,HTML 是错的。 下面所有内容都来自我维护的五个 Next.js 16.2.10 / React 19.2.7 代码库。
通讯
我们发布新模板或撰写值得阅读的文章时会发送邮件。没有杂音。