スタジオジャーナル
フロントエンド・エンジニアリング、ミニマルデザイン、そしてプレミアムなデジタル体験を支える技術的な手仕事についての深堀り。
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 は woff2 ファイルを fonts.gstatic.com から ビルド時に ダウンロードする。Google がファイルのハッシュをローテートしたとき、deploy が 404 で落ち始めたが、ローカルでは再現できなかった。同じ fetch が数秒前に自分のマシンではすでに成功していたからだ。修正方法: フォントをリポジトリに vendoring して next/font/local を使う。そうすればバイト列は自分が所有する入力になる。
filter と backdrop-filter では、Lightning CSS がプレフィックス付きとプレフィックスなしの宣言を1つにマージし、最後に書いたほうのプレフィックスフラグを残す。つまり backdrop-filter の後ろに -webkit-backdrop-filter を書くと、WebKit 専用にコンパイルされる。Chrome は -webkit-backdrop-filter をまったくサポートしていないので、ソースは正しく見えたまま、本番では blur が黙って消える。他のプレフィックス付きプロパティはたいてい同じことをしても無事なので、なおさら見落としやすい。
Tailwind v4 では、@theme の中に --z-overlay: 200 と書いても z-overlay ユーティリティは作られない。ユーティリティが値を読むのは名前空間からで、z- が見るのは --z-index- だ。 エラーも警告も出ない。クラスが単に存在せず、要素は z-index: auto にフォールバックし、正の z-index を持つものが上に描画される — しかも一部のルートだけで起きて、他では起きない。 決着をつけられるチェックは、ソースではなくビルド後の CSS をクラス名で grep することだ。
最初のレンダーが document、matchMedia、location.hash、localStorage、あるいは Cookie に依存しているなら、値が 1 つ食い違うだけで hydration mismatch になる。これらの API は SSR 中には存在せず、クライアントの最初のレンダーではすでに本物の値になっているからだ。 それでも tsc、ESLint、Node 環境のユニットテスト一式はすべてパスする。どれ一つとして hydration レンダーを実行しないからだ。 誠実な修正は 2 つ。マウント後に seed する(簡単だが 1 フレームちらつく)か、値をサーバー側で読む(ちらつかないが Cookie の往復が要る)。
params は Promise になり、metadata はフィールド単位でマージされて深くはマージされず、PageProps/LayoutProps は何かがそれを生成するまで存在せず、ペイント前に走るテーマスクリプトはサーバーレンダリングでなければならない。 4 つのうち 3 つは何も言わずに壊れる。build は緑、型も緑、出てくる HTML が間違っている。 以下はすべて、自分がメンテナンスしている Next.js 16.2.10 / React 19.2.7 のコードベース 5 つから出てきたものだ。
ニュースレター
新しいテンプレートをリリースしたり、読む価値のある記事を書いたときにメールをお送りします。雑音なし。