Next.js 16.4.1 e Node.js de outubro: o checklist de patch para apps em produção
Duas RCEs críticas no Next.js e 4 CVEs altas no Node.js em uma semana. O que muda, quem é afetado e como automatizar a checagem no CI.
- seguranca
- nextjs
- backend
O Next.js 16.4 liga Cache Components e Partial Prefetching por padrão. Como decidir o que prefetchar, o que guardar em cache e o que deixar para depois.

Igor Calabraro
· 7 min de leitura
Durante anos, o prefetch no Next.js foi quase uma escolha binária: ou você buscava a página de destino inteira antes do clique, inclusive a parte dinâmica, ou desligava o prefetch e jogava todo o trabalho para depois do clique. Com o Next.js 16.4, lançado em 6 de outubro, isso mudou de vez. Minha tese: a pergunta deixou de ser "prefetchar ou não" e virou "qual pedaço de cada página precisa estar pronto antes do clique". Neste post eu mostro como tomar essa decisão, com código, a partir do estudo de caso que a Vercel publicou em 16 de outubro sobre o v0.
O Partial Prefetching chegou no 16.3, e o 16.4 deu o passo que importa para projetos novos: o create-next-app agora já vem com Cache Components e Partial Prefetching ligados, como modelo recomendado para todo app Next.js.
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
};
export default nextConfig;Na prática, o framework passa a prefetchar só as partes reutilizáveis da rota. O resto carrega durante a navegação. Conteúdo em cache, inclusive conteúdo personalizado guardado no navegador, pode entrar no prefetch e ser reaproveitado quando o usuário volta à página. Em apps existentes, dá para adotar rota por rota com export const prefetch = "partial" no arquivo da página.
O ponto central do estudo de caso é separar cada página em pedaços e decidir o destino de cada um. Eu resumiria assim:
| Tipo de conteúdo | Onde fica o cache | API | Como invalidar |
|---|---|---|---|
| Público, igual para todos (catálogo, template, post) | Cache compartilhado do servidor | "use cache: remote" + cacheTag() + cacheLife() | revalidateTag() quando o dado muda |
| Personalizado (configurações, painel do usuário) | Navegador do usuário | "use cache: private" + cacheLife() | updateTag() na Server Action que altera o dado |
| Baixa prioridade (comentários, histórico, "recentes") | Cache, mas fora do prefetch | await navigation() antes da busca | Igual ao bloco em que está |
Um exemplo com Supabase: a página de produto de uma loja. O detalhe do produto é igual para todo mundo, então pode ir para o cache compartilhado. Repare que o cliente do Supabase aqui não lê cookies: ele usa a chave pública, então o RLS só devolve o que o papel anônimo pode ver. Isso evita colocar dado de um usuário num cache que todos leem.
// lib/produtos.ts
import { createClient } from "@supabase/supabase-js";
import { cacheLife, cacheTag } from "next/cache";
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL ?? "",
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "",
);
export async function getProdutoPublico(id: string) {
"use cache: remote";
cacheTag(`produto:${id}`);
cacheLife("hours");
const { data, error } = await supabase
.from("produtos")
.select("id, nome, descricao, preco")
.eq("id", id)
.single();
if (error) throw error;
return data;
}Quando o produto é editado no painel administrativo, a Server Action marca o cache como velho:
// app/admin/produtos/actions.ts
"use server";
import { revalidateTag } from "next/cache";
export async function publicarProduto(id: string) {
// ...grava no banco
revalidateTag(`produto:${id}`, "max");
}Telas de configuração e painéis por usuário não podem ir para um cache compartilhado. O "use cache: private" mantém o resultado no navegador. No caso do v0, as páginas de configurações recebem o escopo da conta como argumento, o que coloca esse valor na chave do cache e separa os dados de cada conta. Para dados que o próprio usuário altera, o par é sempre leitura em cache + updateTag() na Server Action, que expira o cache na hora e mostra o valor novo na mesma ida ao servidor.
Esse é o cenário de sistemas como o CRM sob medida do meu portfólio, que tem acesso por permissão: o que cada pessoa vê depende de quem ela é, então esse conteúdo é candidato a cache privado, nunca ao compartilhado.
A API nova do 16.4 é navigation(), importada de next/cache. Todo trabalho em cache que vem depois dela espera a página abrir, mas continua disponível quando o usuário volta. Serve para blocos úteis, porém não urgentes:
// app/produtos/[id]/avaliacoes.tsx
import { navigation } from "next/cache";
export async function Avaliacoes({ produtoId }: { produtoId: string }) {
await navigation();
const avaliacoes = await getAvaliacoes(produtoId);
return <ListaDeAvaliacoes itens={avaliacoes} />;
}Na página, esse componente fica dentro de um Suspense com skeleton próprio. O título, o preço e o botão de compra chegam com o prefetch, e as avaliações carregam logo depois.
Aqui está o detalhe que mais me interessou no estudo de caso. Na primeira versão, o time colocou prefetch={true} em todos os cards da página de templates do v0. O conteúdo ficou pronto antes do clique, mas as requisições de prefetch para a rota de detalhe subiram 2,9×, porque cada card visível disparava um prefetch completo.
A correção foi esperar um sinal de intenção: hover, foco ou toque. Um componente reutilizável para isso:
// components/intent-link.tsx
"use client";
import Link from "next/link";
import { type ReactNode, useState } from "react";
interface IntentLinkProps {
href: string;
children: ReactNode;
className?: string;
}
export function IntentLink({ href, children, className }: IntentLinkProps) {
const [intent, setIntent] = useState(false);
const marcarIntencao = () => setIntent(true);
return (
<Link
href={href}
className={className}
prefetch={intent ? true : "auto"}
onMouseEnter={marcarIntencao}
onFocus={marcarIntencao}
onTouchStart={marcarIntencao}
>
{children}
</Link>
);
}Minha regra prática: grupos pequenos e fixos (menu, abas, três ou quatro links principais) podem prefetchar quando aparecem na tela. Listas e grades grandes usam intenção.
O estudo de caso usa o helper instant() do pacote @next/playwright. Ele segura o conteúdo que só chegaria durante a navegação, então o teste consegue afirmar o que já estava pronto antes do clique. Se alguém remover o cache de um bloco no futuro, o teste quebra no CI. É o tipo de garantia que eu prefiro a "parece rápido na minha máquina".
Navegação instantânea não é enfeite: é o que faz um painel interno parecer um app e uma loja não perder o cliente entre a listagem e o produto. Mas prefetch sem critério custa requisição, CPU e banda. O que eu quero mostrar com esse tipo de análise é o raciocínio que uso em projeto: separar dado público de dado do usuário, decidir o que precisa estar pronto antes do clique e proteger essa decisão com teste.
"use cache: remote", personalizado para "use cache: private", e o que pode esperar fica depois de navigation().instant() no CI e confira onde o cache remoto mora se o app não roda na Vercel.Quer esse cuidado no seu projeto?
Diagnóstico gratuito de 30 min: olho a sua stack e digo o que precisa de atenção.
Quero meu diagnósticoDuas RCEs críticas no Next.js e 4 CVEs altas no Node.js em uma semana. O que muda, quem é afetado e como automatizar a checagem no CI.
O mesmo cuidado dos posts, aplicado ao seu produto. Diagnóstico gratuito de 30 min, sem compromisso.