kb.erickguedes.com
Next.js: React Full-Stack

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.all para data fetching paralelo. Suspense com streaming permite renderização progressiva. SSG com force-cache, SSR com no-store, ISR com revalidate.