Rendering e Data Fetching
Aula 2 de 6
SSR, SSG e ISR
SSR (Server-Side Rendering)
// app/products/page.tsx
// SSR é o padrão quando fetch não tem cache configurado
export default async function ProductsPage() {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
SSG (Static Site Generation)
// app/about/page.tsx
// Dados são buscados em build time
export default async function AboutPage() {
const res = await fetch('https://api.example.com/about', {
cache: 'force-cache' // SSG: busca uma vez no build
});
const data = await res.json();
return <div>{data.content}</div>;
}
ISR (Incremental Static Regeneration)
// app/blog/[slug]/page.tsx
export default async function PostPage({
params,
}: {
params: { slug: string };
}) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: 3600 } // ISR: regera a cada 1 hora
});
const post = await res.json();
return <article>{post.content}</article>;
}
generateStaticParams
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post: { slug: string }) => ({
slug: post.slug
}));
}
// dynamicParams: false = 404 para slugs não gerados
export const dynamicParams = false;
Cache e Fetch
// Estratégias de cache com fetch
const data = await fetch(url, {
cache: 'force-cache' // SSG - cache permanente (build time)
});
const data = await fetch(url, {
cache: 'no-store' // SSR - sempre buscar frescos
});
const data = await fetch(url, {
next: { revalidate: 60 } // ISR - cache com revalidação
});
// Tags para revalidação manual
const data = await fetch(url, {
next: { tags: ['products'] }
});
// Revalidar via Server Action
import { revalidateTag } from 'next/cache';
revalidateTag('products');
Server Components vs Client Components
Server Component (padrão)
// app/users/page.tsx — Server Component (padrão)
import { db } from '@/lib/db';
export default async function UsersPage() {
const users = await db.user.findMany();
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
);
}
Client Component
// app/users/user-list-client.tsx
'use client';
import { useState } from 'react';
type User = { id: number; name: string; email: string };
export function UserListClient({ initialUsers }: { initialUsers: User[] }) {
const [users] = useState(initialUsers);
const [search, setSearch] = useState('');
const filtered = users.filter(u =>
u.name.toLowerCase().includes(search.toLowerCase())
);
return (
<div>
<input
type="text"
placeholder="Buscar..."
onChange={(e) => setSearch(e.target.value)}
className="border p-2 rounded"
/>
<ul>
{filtered.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
Composição: Server + Client
// app/users/page.tsx — Server Component
import { db } from '@/lib/db';
import { UserListClient } from './user-list-client';
export default async function UsersPage() {
const users = await db.user.findMany();
// Passar dados do servidor para o cliente
return <UserListClient initialUsers={users} />;
}
Data Fetching Paralela vs Sequencial
Sequencial (lento)
// app/dashboard/page.tsx
export default async function DashboardPage() {
const user = await fetch('/api/user').then(r => r.json()); // 200ms
const posts = await fetch('/api/posts').then(r => r.json()); // +200ms
const notifications = await fetch('/api/notifications')
.then(r => r.json()); // +200ms
// Total: ~600ms
return <Dashboard user={user} posts={posts} notifications={notifications} />;
}
Paralela (rápido)
// app/dashboard/page.tsx
export default async function DashboardPage() {
// Todas as chamadas disparam simultaneamente
const [user, posts, notifications] = await Promise.all([
fetch('https://api.example.com/user').then(r => r.json()),
fetch('https://api.example.com/posts').then(r => r.json()),
fetch('https://api.example.com/notifications').then(r => r.json())
]);
// Total: ~200ms (a mais lenta)
return <Dashboard user={user} posts={posts} notifications={notifications} />;
}
React Suspense Boundaries
// app/dashboard/page.tsx
import { Suspense } from 'react';
async function UserProfile() {
const user = await fetch('https://api.example.com/user')
.then(r => r.json());
return <div>{user.name}</div>;
}
async function UserPosts() {
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json());
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Carregando perfil...</div>}>
<UserProfile />
</Suspense>
<Suspense fallback={<div>Carregando posts...</div>}>
<UserPosts />
</Suspense>
</div>
);
}
Streaming com Suspense
// app/page.tsx
import { Suspense } from 'react';
import { SlowComponent } from './slow-component';
import { FastComponent } from './fast-component';
export default function HomePage() {
return (
<div>
{/* Renderiza imediatamente */}
<FastComponent />
{/* Streama quando pronto */}
<Suspense fallback={<div>Carregando...</div>}>
<SlowComponent />
</Suspense>
</div>
);
}
Lab: Exercício - Dashboard com Data Fetching
// app/dashboard/page.tsx
import { Suspense } from 'react';
async function RevenueChart() {
const data = await fetch('https://api.example.com/revenue', {
next: { revalidate: 300 }
}).then(r => r.json());
return <RevenueChartComponent data={data} />;
}
async function RecentSales() {
const sales = await fetch('https://api.example.com/sales', {
cache: 'no-store'
}).then(r => r.json());
return <SalesTable sales={sales} />;
}
async function TopProducts() {
const products = await fetch('https://api.example.com/products/top', {
next: { tags: ['top-products'] }
}).then(r => r.json());
return <ProductList products={products} />;
}
export default function DashboardPage() {
return (
<div className="grid grid-cols-3 gap-4">
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentSales />
</Suspense>
<Suspense fallback={<ListSkeleton />}>
<TopProducts />
</Suspense>
</div>
);
}
# Build
npm run build
# Produção
npm start
Server Components são o padrão e reduzem JavaScript no cliente. Use
Promise.allpara data fetching paralelo. Suspense com streaming permite renderização progressiva. SSG comforce-cache, SSR comno-store, ISR comrevalidate.