Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Estas receitas de habilidade são projetadas para o Claude Code, mas também funcionam com outros agentes de codificação de IA que suportam arquivos de habilidade/instrução.
O conteúdo completo do SKILL.md que você pode copiar para .claude/skills/nextjs-data-fetching/SKILL.md:
---
name: nextjs-data-fetching
description: "Dominando a busca de dados com Next.js 15+, streaming, prerendering parcial e estratégias de cache. Use quando solicitado para: busca de dados, estratégia de cache, streaming, revalidação, server actions, padrões de fetch, prevenção de waterfall, PPR."
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Agent"
---
# Busca de Dados no Next.js
Você é um especialista em busca de dados no Next.js. Forneça orientação autoritária sobre busca, cache, revalidação, streaming e Server Actions.
## Matriz de Decisão de Estratégia de Busca
| Cenário | Estratégia | Onde |
|---|---|---|
| Conteúdo de página estática | fetch em tempo de build | Server Component |
| Dados específicos do usuário | fetch em tempo de requisição | Server Component com cookies/headers |
| Submissão de formulário | Server Action | função "use server" |
| Atualizações em tempo real | Fetch no lado do cliente (SWR ou React Query) | Client Component |
| Lista + pré-busca de detalhes | Fetches paralelos com Promise.all | Server Component |
| Scroll infinito | Fetch no lado do cliente com paginação | Client Component |
| Pesquisa com parâmetros de URL | searchParams na página | Server Component |
| Mutação otimista | useOptimistic + Server Action | Client Component |
## Busca em Server Components
### Padrão Básico
```tsx
// app/posts/page.tsx - Server Component (padrão)
async function getPosts() \{
const res = await fetch("https://api.example.com/posts", \{
next: \{ revalidate: 3600 \}, // ISR: revalida a cada hora
\});
if (!res.ok) throw new Error("Falha ao buscar posts");
return res.json() as Promise<Post[]>;
\}
export default async function PostsPage() \{
const posts = await getPosts();
return (
<ul>
\{posts.map((post) => (
<li key=\{post.id\}>\{post.title\}</li>
))\}
</ul>
);
\}// RUIM: Waterfall sequencial
async function Page() \{
const user = await getUser(); // 200ms
const posts = await getPosts(); // 300ms
// Total: 500ms
// BOM: Busca paralela
async function Page() \{
const [user, posts] = await Promise.all([
getUser(), // 200ms
getPosts(), // 300ms
]);
// Total: 300ms (máximo de ambos)export default async function SearchPage(\{
searchParams,
\}: \{
searchParams: Promise<\{ q?: string; page?: string \}>;
\}) \{
const \{ q, page \} = await searchParams;
const results = await search(q ?? "", Number(page ?? "1"));
return <SearchResults results=\{results\} />;
\}Next.js 15 mudou o padrão: requisições fetch NÃO SÃO MAIS cacheadas por padrão.
// Optar pelo cache
fetch(url, \{ cache: "force-cache" \});
// Cache com revalidação baseada em tempo
fetch(url, \{ next: \{ revalidate: 3600 \} \});
// Sem cache (padrão no Next.js 15)
fetch(url, \{ cache: "no-store" \});
// ou simplesmente: fetch(url) - no-store é o padrão// Revalidação baseada em tempo
fetch(url, \{ next: \{ revalidate: 60 \} \});
// Revalidação sob demanda por caminho
import \{ revalidatePath \} from "next/cache";
revalidatePath("/posts");
// Revalidação sob demanda por tag
import \{ revalidateTag \} from "next/cache";
// Ao buscar:
fetch(url, \{ next: \{ tags: ["posts"] \} \});
// Ao mutar:
revalidateTag("posts");import \{ unstable_cache \} from "next/cache";
const getCachedUser = unstable_cache(
async (id: string) => db.user.findUnique(\{ where: \{ id \} \}),
["user"], // partes da chave de cache
\{ revalidate: 3600, tags: ["user"] \}
);import \{ Suspense \} from "react";
export default function DashboardPage() \{
return (
<div>
<h1>Dashboard</h1>
\{/* Isso renderiza imediatamente */\}
<StaticHeader />
\{/* Estes transmitem independentemente */\}
<Suspense fallback=\{<ChartSkeleton />\}>
<RevenueChart />
</Suspense>
<Suspense fallback=\{<TableSkeleton />\}>
<RecentOrders />
</Suspense>
</div>
);
\}
// Cada componente async transmite quando pronto
async function RevenueChart() \{
const data = await getRevenue(); // consulta lenta
return <Chart data=\{data\} />;
\}
async function RecentOrders() \{
const orders = await getOrders(); // outra consulta lenta
return <OrdersTable orders=\{orders\} />;
\}PPR combina conteúdo estático e dinâmico em uma única rota:
// next.config.ts
const config = \{
experimental: \{
ppr: true,
\},
\};
// app/product/[id]/page.tsx
import \{ Suspense \} from "react";
export default async function ProductPage(\{
params,
\}: \{
params: Promise<\{ id: string \}>;
\}) \{
const \{ id \} = await params;
const product = await getProduct(id); // estático (em cache)
return (
<div>
\{/* Shell estático - pré-renderizado */\}
<h1>\{product.name\}</h1>
<p>\{product.description\}</p>
\{/* Buracos dinâmicos - transmitidos em tempo de requisição */\}
<Suspense fallback=\{<PriceSkeleton />\}>
<DynamicPrice productId=\{id\} />
</Suspense>
<Suspense fallback=\{<ReviewsSkeleton />\}>
<DynamicReviews productId=\{id\} />
</Suspense>
</div>
);
\}// actions.ts
"use server";
import \{ revalidatePath \} from "next/cache";
import \{ redirect \} from "next/navigation";
export async function createPost(formData: FormData) \{
const title = formData.get("title") as string;
const body = formData.get("body") as string;
// Sempre valide no lado do servidor
if (!title || title.length < 3) \{
return \{ error: "O título deve ter pelo menos 3 caracteres" \};
\}
await db.post.create(\{ data: \{ title, body \} \});
revalidatePath("/posts");
redirect("/posts");
\}// lib/data.ts
export function preloadUser(id: string) \{
void getUser(id); // dispara o fetch, descarta a promise (o cache de memoização de requisição o armazena)
\}
// app/user/[id]/page.tsx
import \{ preloadUser \} from "@/lib/data";
export default async function UserPage(\{
params,
\}: \{
params: Promise<\{ id: string \}>;
\}) \{
const \{ id \} = await params;
preloadUser(id); // inicia a busca imediatamente
// ... outro trabalho ...
const user = await getUser(id); // atinge o cache de memoização
return <UserProfile user=\{user\} />;
\}
## Exemplo de Trabalho
### Exemplo 1: Usuário pergunta "Como devo buscar dados para meu dashboard?"
**Prompt do usuário:** "Meu dashboard tem informações do usuário, atividade recente e gráficos de análise. É lento porque tudo carrega sequencialmente."
**A resposta guiada pela habilidade incluiria:**
```tsx
// app/dashboard/page.tsx
import { Suspense } from "react";
import { UserInfoSkeleton, ActivitySkeleton, ChartSkeleton } from "./skeletons";
export default function DashboardPage() {
return (
<div className="grid grid-cols-12 gap-6">
<div className="col-span-4">
<Suspense fallback={<UserInfoSkeleton />}>
<UserInfo />
</Suspense>
</div>
<div className="col-span-8">
<Suspense fallback={<ChartSkeleton />}>
<AnalyticsChart />
</Suspense>
</div>
<div className="col-span-12">
<Suspense fallback={<ActivitySkeleton />}>
<RecentActivity />
</Suspense>
</div>
</div>
);
}
// Cada componente busca independentemente - sem waterfalls
async function UserInfo() {
const user = await getCurrentUser();
return <UserCard user={user} />;
}
async function AnalyticsChart() {
const data = await getAnalytics();
return <Chart data={data} />;
}
async function RecentActivity() {
const activity = await getActivity();
return <ActivityFeed items={activity} />;
}
A resposta guiada pela habilidade recomendaria:
revalidate: 3600 para listas de produtosgenerateStaticParams para os produtos mais popularesEsta habilidade equipa o Claude com:
Estenda esta habilidade adicionando:
fetchApi")mkdir -p .claude/skills/nextjs-data-fetching
# Cole o conteúdo da Receita em .claude/skills/nextjs-data-fetching/SKILL.mdforce-cache era o padrão.revalidatePath("/posts/[id]") revalida TODOS os posts, não apenas um.| Abordagem | Quando Usar |
|---|---|
| SWR | Busca no lado do cliente com revalidação automática |
| TanStack Query | Gerenciamento complexo de cache no lado do cliente |
| tRPC | Camada de API type-safe de ponta a ponta |
| GraphQL (Apollo ou Relay) | Requisitos de dados complexos com relacionamentos |
fetch usava force-cache por padrão (requisições eram cacheadas)fetch usa no-store por padrão (requisições NÃO SÃO cacheadas)cache: "force-cache" ou next: { revalidate: N }// Use Promise.all para fetches independentes
const [user, posts] = await Promise.all([
getUser(),
getPosts(),
]);await sequencialmente quando os fetches são independentes<Suspense>export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ q?: string; page?: string }>;
}) {
const { q, page } = await searchParams;
// ...
}searchParams é uma Promise no Next.js 15+ e deve ser aguardadafetch(url, { next: { revalidate: 60 } }) regenera após 60 segundosrevalidatePath("/posts") invalida imediatamente quando chamadanext: { tags: ["posts"] }, então chama revalidateTag("posts")revalidatePath("/posts/[id]") revalida TODAS as páginas que correspondem a esse segmento dinâmico<Suspense> com fallbacks de esqueletoexperimental: { ppr: true } em next.config.tsexport function preloadUser(id: string) {
void getUser(id); // dispara o fetch, descarta a promise
}
// Mais tarde no componente:
preloadUser(id);
const user = await getUser(id); // atinge o cache de memoizaçãoimport { unstable_cache } from "next/cache";
const getCachedUser = unstable_cache(
async (id: string) => db.user.findUnique({ where: { id } }),
["user"],
{ revalidate: 3600, tags: ["user"] }
);unstable_cache para consultas ORM e outras fontes de dados não-fetchsearchParams torna a rota dinâmica"use server";
export async function createPost(formData: FormData) {
const title = formData.get("title") as string;
// Sempre valide no lado do servidor com Zod
const parsed = Schema.safeParse({ title });
if (!parsed.success) return { error: "Inválido" };
}FormData, não como um objeto tipadoRevisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥