Diário do estúdio
Um mergulho na engenharia frontend, no design minimal e no ofício técnico por trás das experiências digitais premium.
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.
o next/font/google descarrega ficheiros woff2 de fonts.gstatic.com em build time. Quando a Google trocou o hash de um ficheiro, o meu deploy começou a falhar com 404 que eu não conseguia reproduzir localmente, porque esse mesmo fetch já tinha corrido bem na minha máquina num segundo anterior. Correção: passar as fontes para o repositório e usar next/font/local, para que os bytes sejam um input teu.
Para filter e backdrop-filter, o Lightning CSS funde a declaração com prefixo e a declaração sem prefixo numa só, mantendo as flags de prefixo daquela que escreveste por último. Ou seja, backdrop-filter seguido de -webkit-backdrop-filter compila para WebKit apenas. O Chrome não suporta de todo -webkit-backdrop-filter, por isso o blur desaparece silenciosamente em produção enquanto o código-fonte parece correto. A maioria das outras propriedades com prefixo sobrevive ao mesmo tratamento sem dano — que é precisamente o que torna esta fácil de deixar passar.
No Tailwind v4, --z-overlay: 200 dentro de @theme não cria um utilitário z-overlay. Os utilitários leem a partir de namespaces, e o de z- é --z-index-. Não há erro nem warning. A classe simplesmente não existe, o elemento cai para z-index: auto, e qualquer coisa com um z-index positivo a sério pinta por cima — nalgumas rotas sim, noutras não. A verificação que resolve mesmo a questão é fazer grep do nome da classe no CSS compilado, e não no código-fonte.
Se o teu primeiro render depende de document, matchMedia, location.hash, localStorage ou de uma cookie, estás a um valor diferente de um hydration mismatch — essas APIs não existem durante o SSR e já são reais no primeiro render do cliente. tsc, o ESLint e uma suite de testes unitários em ambiente Node passam à mesma, porque nenhum deles chega a fazer um render de hydration. Duas correções honestas: semear depois do mount (simples, um frame de flash), ou ler o valor no servidor (sem flash, exige uma ida e volta com cookie).
params é uma Promise, os metadados fazem merge campo a campo e não em profundidade, PageProps/LayoutProps só existem quando algo os gera, e um script de tema pre-paint tem de ser renderizado no servidor. Três dos quatro falham em silêncio: build verde, tipos verdes, HTML errado. Tudo o que se segue vem de cinco projetos com Next.js 16.2.10 / React 19.2.7 que mantenho.
Newsletter
Um email quando lançamos um novo template ou escrevemos algo que vale o seu tempo. Sem ruído.