Capítulo 46 de 456
Guia prático para estruturar rotas de forma que o navegador comece a renderizar a próxima página no instante do clique (conteúdo estático/cacheado/fallback aparecendo imediatamente), usando Cache Components + Partial Prefetching, validação automática em dev, DevTools de inspeção e testes e2e com instant().
<Link> sob Partial Prefetching.prefetch={true} (per-link prefetching): resolve params/searchParams/URL completa de um link específico antes do clique; não substitui ter o App Shell instantâneo primeiro.validationLevel: opção em experimental.instantInsights; 'warning' (padrão) valida toda Page/Default em dev; 'manual-warning' só valida segmentos que exportam instant explicitamente.instant = false: route segment config que opta um segmento fora da validação (a rota ainda pode navegar instantaneamente se a estrutura suportar, só não gera insights).instant() helper (@next/playwright): escopa asserções à UI imediatamente disponível na navegação, para testes e2e de regressão.exposeTestingApiInProductionBuild: flag experimental que expõe a testing API do instant() também em next start (build de produção), não só em next dev."use cache: private": variante de cache para funções que leem cookies()/headers(); cache só no browser, não pode fazer parte do static shell.// next.config.ts
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
}
// app/products/[slug]/page.tsx — depois do fix
async function ProductInfo({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const res = await fetch(`https://next-recipe-api.vercel.dev/products/${slug}`)
const product = await res.json()
return <><h1>{product.name}</h1><p>${product.price}</p></>
}
async function getFeatured() {
'use cache'
const res = await fetch('https://next-recipe-api.vercel.dev/products?limit=3')
return res.json()
}
export default async function ProductPage(props: PageProps<'/products/[slug]'>) {
const featured = await getFeatured()
return (
<div>
<FeaturedSection items={featured} />
<Suspense fallback={<p>Loading product...</p>}>
<ProductInfo params={props.params} />
</Suspense>
</div>
)
}
<Suspense>, e cachear a leitura independente de URL com 'use cache'.// e2e/navigation.test.ts
import { instant } from '@next/playwright'
test('is instant on a client navigation', async ({ page }) => {
await page.goto('/store/shoes')
await instant(page, async () => {
await page.click('a[href="/store/hats"]')
await page.waitForURL((url) => url.pathname === '/store/hats')
await expect(page.locator('h1')).toContainText('Baseball Cap')
await expect(page.getByText('In stock')).toHaveCount(0)
})
await expect(page.getByText('In stock')).toBeVisible()
})
instant() e o dado dinâmico chegando depois.<Suspense> só no topo da página para "passar" na validação: satisfaz a validação, mas substitui quase toda a página por um único fallback em cada navegação; prefira fallbacks pequenos e localizados.<Suspense> do root layout cobrir navegação client-side: só cobre visita direta (page load); navegação client-side só re-renderiza abaixo do layout compartilhado."use cache: private" esperando que entre no static shell: esse cache é só de browser; não é servido no App Shell.prefetch={true} só resolve dados por-link; primeiro é preciso tornar a rota instantânea com App Shell — per-link prefetching não conserta uma rota que bloqueia sem ele.instant() em testes e2e fecha a lacuna que a validação estrutural não cobre: garante que o conteúdo certo (não só a existência de um shell) aparece na navegação.instant = false desliga só o feedback de validação, não a capacidade da rota de navegar instantaneamente se a estrutura já suportar.'use cache', prefetch={true}) para tornar navegação repetida instantânea."use cache: private".