React: Introdução ao Next.js
Última atualização: 2026-08-26
Tom criou um blog usando o
create-react-app. Após o lançamento, ele percebeu que o carregamento da primeira tela levava 3 segundos, os mecanismos de busca não conseguiam rastrear o conteúdo e ele precisava atualizar manualmente o cache da CDN toda vez que publicava um post. Ele percebeu que a renderização apenas no lado do cliente não era suficiente — ele precisava de um framework capaz de renderização no lado do servidor, geração estática e otimização automática. Foi aí que o Next.js entrou em cena.
O Next.js, desenvolvido e mantido pela Vercel, é atualmente o framework React full-stack mais popular. Ele resolve questões fundamentais que o React, como uma biblioteca de interface do usuário puramente do lado do cliente, não consegue lidar: sistemas de roteamento, renderização do lado do servidor, geração de sites estáticos, roteamento de API, otimização de imagens, otimização de fontes, middleware e muito mais. Seja para criar um blog, um site de comércio eletrônico, um aplicativo SaaS ou um aplicativo de nível empresarial, o Next.js oferece as melhores práticas prontas para uso.
Esta aula começa esclarecendo os conceitos fundamentais e o projeto arquitetônico do Next.js. Você aprenderá as diferenças entre os quatro modos de renderização, como funciona o App Router e a divisão de tarefas entre os componentes do lado do servidor e do lado do cliente. Esses conceitos fundamentais servem de base para o estudo aprofundado da obtenção de dados, da implantação e de projetos práticos abrangentes. Por meio do estudo de caso de migração real do Tom, você obterá uma compreensão clara de todo o caminho técnico de migração do CRA para o Next.js.
1. O que você vai aprender
- CSR x SSR x SSG x ISR: diferenças e critérios de seleção para os quatro modos de renderização
- Mecanismo de roteamento baseado em arquivos do Next.js App Router
- Projeto e reutilização de sistemas de layout aninhados
- Divisão de tarefas e colaboração entre o componente servidor e o componente cliente
- Caminhos de migração para o Pages Router e o App Router
- Como configurar rotas dinâmicas, rotas genéricas e rotas de API
- Utilização de componentes de estado de rota, como “Carregando”, “Erro” e “Não encontrado”
- Um guia prático para a migração de um projeto CRA para o Next.js
- Árvore de decisão para seleção do modo de renderização: Selecione a solução ideal com base no tipo de página e nas características dos dados
- Métricas de desempenho: comparação entre o tempo de carregamento da primeira tela, a pontuação de SEO e o TTFB (Time to First Byte) em diferentes modos de renderização
2. Diagramas conceituais
Enquanto aprendia Next.js, Tom desenhou um fluxograma para entender o caminho de processamento das solicitações. Quando uma solicitação do usuário chega, o Next.js seleciona um caminho de renderização diferente com base na configuração da página (estática, dinâmica ou incremental), retornando, por fim, HTML e permitindo a interação no navegador.
Este diagrama ilustra quatro pontos-chave de decisão: primeiro, determinar o tipo de página (estática/dinâmica/incremental/do lado do cliente); em seguida, selecionar o mecanismo de renderização correspondente; gerar o HTML e enviá-lo ao navegador; e, por fim, utilizar o processo de hidratação para permitir que os componentes do lado do cliente na página se tornem interativos.
flowchart LR
A[User Request] --> B{Next.js Route Matching}
B -->|Static Page| C[SSG<br/>Generated during build]
B -->|Dynamic Pages| D[SSR<br/>Render on Request]
B -->|Incremental Update| E[ISR<br/>Re-verify on Demand]
B -->|Client Interaction| F[CSR<br/>Browser Execution]
C --> G[Back HTML]
D --> G
E --> G
F --> G
G --> H[Hydration<br/>Activate Interaction]
H --> I[Client Component<br/>Run JS]
3. Um cenário da vida real
O blog do Tom foi originalmente desenvolvido usando create-react-app, com todas as páginas renderizadas no lado do cliente. Quando os usuários abriam um artigo, primeiro baixavam um modelo HTML vazio e, em seguida, aguardavam o término do carregamento do JavaScript antes de buscar os dados da API para renderizar o conteúdo. Isso era um desastre para um site voltado para conteúdo — os rastreadores dos mecanismos de busca viam apenas páginas em branco, e o tempo até o primeiro conteúdo (TFC) chegava a 3 segundos.
Após realizar sua pesquisa, ele descobriu que era necessária uma abordagem de “renderização híbrida”: as postagens do blog seriam geradas estaticamente (HTML gerado durante o processo de compilação e distribuído por meio de uma CDN), as páginas de perfil dos usuários seriam renderizadas no lado do servidor (com os dados mais recentes gerados a cada solicitação) e a seção de comentários seria renderizada no lado do cliente (com as interações processadas no navegador). O Next.js, por acaso, oferece tudo isso.
Ele passou uma semana migrando seu blog do CRA para o Next.js, realizando três ações específicas: primeiro, mudou as páginas de posts para SSG, usando o generateStaticParams para gerar HTML para todas as páginas de posts durante o processo de compilação; após a implantação no Vercel, o CDN serve as páginas pré-renderizadas diretamente, reduzindo o tempo até o primeiro conteúdo (TFC) para 0,3 segundos. Em segundo lugar, ele mudou as páginas de perfil de usuário para SSR, buscando os dados mais recentes do banco de dados a cada solicitação; em terceiro lugar, a seção de comentários foi implementada usando componentes do lado do cliente, carregando apenas o JavaScript interativo no navegador. Após a migração, a pontuação de SEO do site no Google Lighthouse melhorou de 45 para 98, e o tempo até o primeiro conteúdo (TFC) foi reduzido em 90%.
Tom também se deparou com vários obstáculos durante a migração. Por exemplo, ele inicialmente usou o Pages Router no diretório pages/, mas depois descobriu que o App Router funcionava melhor, então dedicou um tempo extra para migrar para ele. Ele recomenda usar o App Router diretamente em novos projetos para evitar ter que fazer uma segunda migração. Ele também descobriu que useRouter e usePathname não podem ser usados diretamente nos Componentes de Servidor; em vez disso, essa lógica de roteamento precisa ser extraída para os Componentes de Cliente. Essas lições lhe proporcionaram uma compreensão profunda da filosofia de design do Next.js — “servidor em primeiro lugar, cliente sob demanda”.
(1) Comparação dos modos de renderização
O Next.js oferece quatro modos de renderização, cada um deles projetado para diferentes casos de uso. Compreender as diferenças entre eles é fundamental para escolher a arquitetura correta.
CSR (Client-Side Rendering): O navegador baixa uma estrutura HTML vazia e, em seguida, executa JavaScript para renderizar o conteúdo. O blog do Tom utilizava originalmente esse modelo. As vantagens são uma interatividade fluida e menor carga no servidor; as desvantagens são o carregamento lento da primeira tela e um SEO insatisfatório. É adequado para aplicativos interativos que exigem login do usuário, como painéis de controle e back-ends administrativos.
SSR (Renderização do Lado do Servidor): Cada vez que um usuário faz uma solicitação, o servidor gera dinamicamente e retorna o código HTML completo. Tom utilizou essa abordagem para resolver os problemas de SEO do seu blog, mas, como cada solicitação precisa aguardar que o servidor conclua a renderização antes que uma resposta seja retornada, isso impõe uma carga pesada ao servidor. É adequado para páginas que exigem dados em tempo real, como sites de notícias e páginas de produtos de comércio eletrônico.
SSG (Geração Estática de Sites): Gera arquivos HTML para todas as páginas durante a fase de compilação; após a implantação em uma CDN, os usuários acessam os arquivos estáticos diretamente. Isso oferece a velocidade de carregamento mais rápida e o melhor SEO, mas as atualizações de conteúdo exigem uma recompilação completa. As páginas do blog do Tom são um bom exemplo desse modelo — uma vez que um artigo é escrito, seu conteúdo permanece fixo. Adequado para blogs, sites de documentação e páginas de marketing.
ISR (Geração Estática Incremental): Uma versão aprimorada do SSG que permite que páginas específicas sejam revalidadas e atualizadas sob demanda após a compilação, sem a necessidade de uma recompilação completa do site. Por exemplo, se a página do catálogo de produtos do Tom estiver configurada para ser revalidada a cada 60 segundos, os novos produtos aparecerão em até 60 segundos após serem adicionados. Isso é ideal para páginas de produtos de comércio eletrônico e sites com conteúdo atualizado com frequência.
A estratégia de seleção pode ser resumida da seguinte forma: use SSG para cenários em que o conteúdo é prioridade, SSR para dados em tempo real, CSR para cenários com interação intensa e ISR para cenários com atualizações frequentes nos quais não se deseja reconstruir o site inteiro.
Tabela de comparação de desempenho dos modos de renderização
| Indicador | SSG | ISR | SSR | CSR |
|---|---|---|---|---|
| Velocidade de carregamento da primeira tela | Mais rápida | Rápida | Média | Lenta |
| Otimização para SEO | Alta | Alta | Alta | Baixa |
| Atualidade dos dados | Definida no momento da compilação | Atualizada sob demanda | Dados mais recentes a cada solicitação | Após o carregamento do navegador |
| Carga do servidor | Nenhuma | Baixa | Alta | Baixa |
| Casos de uso | Blogs/Documentação | Comércio eletrônico/Notícias | Páginas personalizadas | Gerenciamento de back-end |
| TTFB típico | <50 ms | <100 ms | 200-500 ms | 200-500 ms |
▶ Exemplo 1: Implementação dos quatro modos de renderização no Next.js
O código abaixo demonstra a implementação dos três modos de renderização no blog do Tom. Observe que generateStaticParams é chamado durante o processo de compilação e retorna todas as combinações possíveis de parâmetros; o Next.js usa isso para gerar HTML estático para todas as páginas. dynamic = 'force-dynamic', por outro lado, força a página a ser renderizada novamente a cada solicitação.
// === Static Generation SSG(Default behavior) ===
// app/blog/[slug]/page.tsx
// Automatically scan all articles during the build process slug,Generate the corresponding HTML Page
// After generation CDN Return static files directly,No server-side rendering required
export async function generateStaticParams() {
// Called during build:Get a list of all articles
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
// Back slug Array,Next.js It will do this for each slug Generate a page
return posts.map((post: any) => ({ slug: post.slug }))
}
async function BlogPost({ params }: { params: { slug: string } }) {
// Each page retrieves data independently when it is rendered.
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json())
return (
<article>
<h1>{post.title}</h1>
<time>{post.publishedAt}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
export default BlogPost
// === Server-Side Rendering SSR(Dynamic Rendering) ===
// app/dashboard/page.tsx
// Every user request starts from API Get the latest data,Ensure the data is up to date
export const dynamic = 'force-dynamic' // Force the static cache to close,Leaving every time SSR
async function DashboardPage() {
// This will be executed with every request fetch
const stats = await fetch('https://api.example.com/dashboard/stats').then(r => r.json())
return (
<div>
<h1>Real-Time Dashboard</h1>
<p>Current Online Users:{stats.onlineUsers}</p>
<p>Today's Orders:{stats.todayOrders}</p>
<p>Income for This Month:${stats.monthlyRevenue}</p>
</div>
)
}
export default DashboardPage
// === Incremental Static Generation ISR ===
// app/products/[id]/page.tsx
// Generate static pages during the build process,But every 60 The first visit after X seconds will trigger a regenerate.
async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
next: { revalidate: 60 } // ISR:60 Please try again in a few seconds.
}).then(r => r.json())
return (
<div>
<h1>{product.name}</h1>
<p>Price:${product.price}</p>
<p>Inventory:{product.stock}</p>
<p>Description:{product.description}</p>
</div>
)
}
export default ProductPage
Em seu blog, Tom optou por uma abordagem híbrida que combina SSG e ISR: ele usa o SSG nas páginas de artigos para garantir que a página inicial carregue rapidamente e o ISR nas páginas de lista de produtos, que atualizam preços e estoque a cada 60 segundos. Essa abordagem garante bons resultados de SEO e, ao mesmo tempo, mantém a atualidade dos dados.
(2) Roteamento de arquivos pelo App Router
O Next.js 13+ apresenta o App Router, que gera rotas automaticamente com base no sistema de arquivos. Basta criar uma pasta chamada app/ e um arquivo chamado page.tsx dentro dessa pasta, e o Next.js mapeia automaticamente os caminhos de URL correspondentes. Você não precisa mais manter manualmente os arquivos de configuração de rotas — a estrutura de diretórios é a própria estrutura de rotas.
Principais tipos de arquivos
page.tsx— Define a página da interface do usuário correspondente a uma rota; é permitido apenas um arquivo desse tipo por pastalayout.tsx— Define o layout compartilhado para este segmento de rota e todas as suas subrotas; suporta aninhamentoloading.tsx— Interface do usuário exibida durante o carregamento dos segmentos da rota, com base no React Suspenseerror.tsx— Interface do usuário exibida quando ocorre um erro no segmento de roteamento; é possível tentar novamente usando a função de reinicialização no arquivo error.tsxnot-found.tsx— Página 404, com precisão até cada segmento da rotaroute.ts— Define pontos de extremidade da API e oferece suporte a métodos como GET, POST, PUT e DELETE
Roteamento dinâmico e modos avançados
Use a sintaxe [param] para criar rotas dinâmicas; por exemplo, app/blog/[slug]/page.tsx corresponde a /blog/hello-world. Use a sintaxe genérica [...param] para corresponder a caminhos de vários níveis; por exemplo, app/docs/[...slug]/page.tsx corresponde a /docs/guide/getting-started/installation.
Os arquivos de layout suportam layouts profundamente aninhados — nos quais um layout pai envolve o conteúdo das páginas filhas. Seções comuns, como a barra de navegação, a barra lateral e o rodapé, precisam ser definidas apenas uma vez; o Next.js gerencia automaticamente o estado persistente do layout. Ao alternar entre páginas, o layout não é renderizado novamente; apenas as páginas filhas são atualizadas.
▶ Exemplo 2: Estrutura completa de roteamento do blog
Isso mostra a estrutura completa de diretórios do App Router para o blog do Tom. Observe que os nomes de arquivos especiais em cada pasta (page, layout, loading, error, not-found) seguem as convenções do Next.js — o framework reconhece automaticamente esses arquivos e atribui a eles comportamentos específicos de roteamento. dashboard/layout.tsx está aninhado dentro da raiz layout.tsx, formando um layout de duas camadas: a camada mais externa consiste na navegação global e no rodapé, enquanto a camada interna contém a barra lateral específica do painel de controle.
app/
├── page.tsx # → / Home
├── layout.tsx # → Global Layout(Navigation + Footer)
├── loading.tsx # → Global Loading State(Skeleton screen during page transitions)
├── error.tsx # → Global Error Page(500 A Flawed Fallback UI)
├── not-found.tsx # → 404 Page
├── about/
│ └── page.tsx # → /about About This Page
├── blog/
│ ├── page.tsx # → /blog Article List Page
│ └── [slug]/ # → Dynamic routing: matches /blog/any-value
│ ├── page.tsx # → /blog/hello-world Article Details
│ └── loading.tsx # → Custom Loading State for Article Detail Pages
├── dashboard/
│ ├── page.tsx # → /dashboard Dashboard Overview
│ ├── layout.tsx # → Dashboard-Exclusive Layout(With sidebar navigation)
│ └── settings/
│ └── page.tsx # → /dashboard/settings Settings Page
└── api/
└── hello/
└── route.ts # → /api/hello API Endpoint
Tabela de referência para mapeamento de rotas
| Caminho do arquivo | URL correspondente | Descrição |
|---|---|---|
app/page.tsx |
/ |
Página inicial |
app/about/page.tsx |
/about |
Rotas estáticas |
app/blog/[slug]/page.tsx |
/blog/:slug |
Roteamento dinâmico |
app/blog/[slug]/loading.tsx |
/blog/:slug |
Carregando estados na mesma rota |
app/dashboard/layout.tsx |
/dashboard/* |
Layout aninhado |
app/api/hello/route.ts |
/api/hello |
Ponto de extremidade da API |
// app/layout.tsx - Global Layout
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh">
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
<a href="/about">About</a>
</nav>
</header>
<main>{children}</main>
<footer>© 2026 Tom 's blog. All rights reserved.</footer>
</body>
</html>
)
}
// app/dashboard/layout.tsx - Dashboard-Exclusive Layout(Nested within the global layout)
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div style={{ display: 'flex' }}>
<aside style={{ width: 240, background: '#f5f5f5', padding: 16 }}>
<nav>
<a href="/dashboard">Overview</a>
<a href="/dashboard/settings">Settings</a>
</nav>
</aside>
<section style={{ flex: 1, padding: 16 }}>{children}</section>
</div>
)
}
// app/blog/[slug]/loading.tsx - Article Details Loading...
export default function Loading() {
return (
<div style={{ padding: 24 }}>
<div style={{ height: 32, width: '60%', background: '#eee', borderRadius: 4, marginBottom: 16 }} />
<div style={{ height: 16, width: '30%', background: '#eee', borderRadius: 4, marginBottom: 24 }} />
<div style={{ height: 200, background: '#eee', borderRadius: 4 }} />
</div>
)
}
(3) Componente do servidor e componente do cliente
Um dos conceitos revolucionários do Next.js é o Componente de Servidor. Isso marca a primeira vez no ecossistema React em que os componentes foram divididos em dois ambientes de execução: lado do servidor e lado do cliente. Por padrão, todos os componentes no App Router são Componentes de Servidor — eles são executados no servidor, podem acessar diretamente o banco de dados, o sistema de arquivos e variáveis de ambiente confidenciais e, o mais importante, não enviam nenhum código JavaScript para o navegador.
Quando você precisa de funcionalidades interativas (como clicar em um botão, inserir texto, usar useState ou useEffect, ou monitorar eventos da janela), um componente do lado do servidor por si só não é suficiente. Nesse caso, você precisa adicionar a declaração 'use client' no início do arquivo. O Next.js marcará então esse componente e todos os seus componentes filhos como Componentes do Cliente e os agrupará para que sejam executados no lado do navegador.
Recursos do componente de servidor
- Você pode usar
async/awaitpara recuperar dados diretamente, sem precisar do useEffect ou do SWR - Acesso direto a bancos de dados, sistemas de arquivos e serviços de back-end
- Não envie JavaScript para o navegador a fim de reduzir o tamanho do pacote
- Acesso seguro às variáveis de ambiente (que não tenham o prefixo NEXT_PUBLIC_)
- Não é possível usar APIs do navegador, como useState, useEffect e onClick
Recursos do Componente Cliente
- Interatividade total com o navegador (estado, eventos, API do DOM)
- Pode ser combinado livremente com os componentes do servidor
- Continua oferecendo suporte ao SSR (renderização do HTML inicial no lado do servidor)
- Quanto maior o arquivo, mais código JavaScript é enviado ao navegador
Modelos de melhores práticas
Ele adota uma arquitetura em camadas, na qual o “Componente Servidor” atua como contêiner e o “Componente Cliente” atua como folha. O Componente Servidor, na camada externa, é responsável por buscar dados e gerenciar o layout, enquanto o Componente Cliente, na camada interna, é responsável apenas pelos widgets que exigem interação. Dessa forma, a maior parte da lógica e da recuperação de dados é tratada no lado do servidor, e o navegador executa apenas o mínimo necessário de JavaScript.
▶ Exemplo 3: Divisão ideal de responsabilidades entre os componentes do servidor e do cliente
O código abaixo ilustra o modelo em camadas descrito no blog do Tom: “Recuperação de dados no servidor, interações no navegador.” PostsPage é um Componente de Servidor — ele busca dados no servidor, os renderiza como HTML e os envia diretamente para o navegador. Os usuários veem a página completa sem precisar esperar o JavaScript carregar. PostList é um componente de cliente — ele recebe os dados preparados pelo servidor e é responsável apenas pelas interações de pesquisa, filtragem e exclusão no lado do navegador. Observe que 'use client' é aplicado apenas a “componentes folha” que exigem interação, e não à página inteira.
// app/posts/page.tsx - Server Component(Default)
// This component runs on the server.,Do not send any JS Go to the browser
import PostList from './PostList'
async function PostsPage() {
// Directly on the server fetch,Data is being compiled HTML It was already ready at that time
// Users see the full page,None loading Status
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return (
<div>
<h1>All Articles</h1>
<PostList posts={posts} />
</div>
)
}
export default PostsPage
// app/posts/PostList.tsx - Annotation 'use client' Become Client Component
// Only this file will be sent JS Go to the browser,The parent component that contains it(Server Component)No
'use client'
import { useState } from 'react'
interface Post {
id: number
title: string
body: string
}
function PostList({ posts: initialPosts }: { posts: Post[] }) {
// useState Only at 'use client' Available in the component
const [posts, setPosts] = useState(initialPosts)
const [search, setSearch] = useState('')
const filtered = posts.filter(p =>
p.title.toLowerCase().includes(search.toLowerCase())
)
function handleDelete(id: number) {
setPosts(prev => prev.filter(p => p.id !== id))
}
return (
<div>
<input
type="text"
placeholder="Search Articles..."
value={search}
onChange={e => setSearch(e.target.value)}
style={{ width: '100%', padding: 8, marginBottom: 16, border: '1px solid #ddd', borderRadius: 4 }}
/>
{filtered.map(post => (
<div key={post.id} style={{
border: '1px solid #ddd', padding: 16, marginBottom: 8, borderRadius: 8,
display: 'flex', justifyContent: 'space-between', alignItems: 'center'
}}>
<div>
<h2 style={{ margin: 0 }}>{post.title}</h2>
<p style={{ margin: '8px 0', color: '#666' }}>{post.body}</p>
</div>
<button
onClick={() => handleDelete(post.id)}
style={{ padding: '4px 12px', background: '#ff4444', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}
>
Delete
</button>
</div>
))}
{filtered.length === 0 && <p>No matching articles were found.</p>}
</div>
)
}
export default PostList
// pages/old-posts.js - Pages Router Pattern(Compatibility with Legacy Projects)
// If you are maintaining Next.js 12 or earlier projects,Pages Router Usage getStaticProps/getServerSideProps
export async function getStaticProps() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return { props: { posts }, revalidate: 60 }
}
export default function OldPostsPage({ posts }: { posts: Post[] }) {
return (
<div>
<h1>Pages Router Pattern</h1>
{posts.map(p => <p key={p.id}>{p.title}</p>)}
</div>
)
}
Restrições dos componentes de servidor versus os componentes de cliente
| Funcionalidade | Componente do servidor | Componente do cliente |
|---|---|---|
| Usar useState/useEffect | ❌ | ✅ |
| Usar onClick/onChange | ❌ | ✅ |
| Acesso direto ao banco de dados | ✅ | ❌ |
| Acessar variáveis de ambiente (sem prefixo) | ✅ | ❌ |
| Enviar JS para o navegador | ❌ | ✅ |
| Usar async/await | ✅ | ❌ |
| Use useRouter/usePathname | ❌ | ✅ |
| Use requestAnimationFrame | ❌ | ✅ |
Fluxograma: Componente de servidor ou componente de cliente?
Components require interaction(Click、Input、Scrolling, etc.)?
├── is → Required useState/useEffect?
│ ├── Yes → add 'use client' → Client Component
│ └── No → only accept props for rendering?
│ ├── is → Retain Server Component
│ └── No → add 'use client'
└── No → keep Server Component
4. Roteiro de migração do Tom
Já abordamos todos os conceitos fundamentais do Next.js. Agora, vamos reunir todo esse conhecimento e ver como o Tom migrou seu blog do CRA para o Next.js, passo a passo. Esse processo também serve como a abordagem padrão para adotar o Next.js em seus próprios projetos.
Tom divide todo o processo de migração de um blog do CRA para o Next.js em cinco etapas, cada uma delas correspondendo a um conceito-chave abordado nesta lição:
- Etapa 1: Inicialização do projeto — Crie um novo projeto usando
create-next-app, selecione “App Router” e migre o código antigosrc/para o diretórioapp/. O ponto-chave desta etapa é compreender as diferenças entre os diretórios do “App Router” e do “Pages Router”. - Etapa 2: Refatoração de rotas — Substitua a configuração de rotas
react-router-domdo CRA pelo roteamento baseado em arquivos do App Router. A configuração manual anterior de rotas (<Routes><Route path="..." /></Routes>) foi transformada em uma estrutura de pastas intuitiva. O roteamento dinâmico agora utiliza a sintaxe[param]em vez da sintaxe:param. - Etapa 3: Selecione um modo de renderização por tipo de página — Escolha a estratégia de renderização adequada para os diferentes tipos de página: use SSG (com
generateStaticParamspara pré-geração) para posts de blog, SSR (comdynamic = 'force-dynamic') para perfis de usuário e CSR + Componente do Cliente (com'use client') para seções de comentários. - Etapa 4: Integração do layout — Extraia o código da barra de navegação e do rodapé, que antes eram duplicados em todas as páginas, para um layout global, reduzindo a duplicação de código em 60%. Use layouts aninhados na área do painel para adicionar a navegação na barra lateral, conseguindo assim a separação de responsabilidades no layout.
- Etapa 5: Otimização e testes — Adicione
loading.tsxpara implementar a tela de esboço durante o carregamento eerror.tsxpara implementar a interface de usuário alternativa em caso de erro. Use o Chrome Lighthouse para verificar as métricas de SEO e desempenho, garantindo que a pontuação de SEO após a migração atinja 90+.
▶ Exemplo 4: Arquivos loading.tsx e error.tsx do App Router
// app/dashboard/loading.tsx - Automatically Displayed Loading Status
export default function DashboardLoading() {
return (
<div style={{ padding: 24 }}>
<div style={{ height: 32, width: 200, background: '#f0f0f0', borderRadius: 4, marginBottom: 16 }} />
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
{[1, 2, 3].map(i => (
<div key={i} style={{
height: 120, background: 'linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)',
backgroundSize: '200% 100%', borderRadius: 8, animation: 'shimmer 1.5s infinite',
}} />
))}
</div>
</div>
)
}
// app/dashboard/error.tsx - Automatically Displayed Error Status
'use client'
export default function DashboardError({ error, reset }) {
return (
<div style={{ padding: 40, textAlign: 'center' }}>
<h2>Something went wrong!</h2>
<p style={{ color: '#999' }}>{error.message}</p>
<button onClick={reset} style={{ padding: '8px 24px', cursor: 'pointer' }}>Try again</button>
</div>
)
}
// app/dashboard/page.tsx - It will automatically use the one above loading and error
async function DashboardPage() {
const data = await fetch('https://api.example.com/dashboard').then(r => r.json())
return (
<div>
<h1>Dashboard</h1>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
{data.stats.map(stat => (
<div key={stat.label} style={{ padding: 16, border: '1px solid #eee', borderRadius: 8 }}>
<p style={{ color: '#999', margin: 0 }}>{stat.label}</p>
<p style={{ fontSize: 24, fontWeight: 'bold', margin: '4px 0 0' }}>{stat.value}</p>
</div>
))}
</div>
</div>
)
}
export default DashboardPage
▶ Exemplo 5: Layouts e modelos aninhados
// app/layout.tsx - Root Layout(Must have html and body)
export default function RootLayout({ children }) {
return (
<html lang="zh">
<body style={{ margin: 0, fontFamily: 'sans-serif' }}>
{children}
</body>
</html>
)
}
// app/(dashboard)/layout.tsx - Dashboard Layout(Route groups have no effect URL)
import Sidebar from '@/components/Sidebar'
export default function DashboardLayout({ children }) {
return (
<div style={{ display: 'flex', minHeight: '100vh' }}>
<Sidebar />
<main style={{ flex: 1, padding: 24 }}>
{children}
</main>
</div>
)
}
// app/(auth)/layout.tsx - Authentication Page Layout(Centered Card)
export default function AuthLayout({ children }) {
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#f5f5f5' }}>
<div style={{ background: 'white', padding: 32, borderRadius: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.1)', width: 400 }}>
{children}
</div>
</div>
)
}
// app/(auth)/login/page.tsx - Login Page
function LoginPage() {
return (
<>
<h2 style={{ textAlign: 'center' }}>Sign In</h2>
<form style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<input type="email" placeholder="Email" style={{ padding: 8, borderRadius: 4 }} />
<input type="password" placeholder="Password" style={{ padding: 8, borderRadius: 4 }} />
<button type="submit" style={{ padding: 10, background: '#1890ff', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>
Sign In
</button>
</form>
</>
)
}
export default LoginPage
❓ Perguntas Frequentes
P: Qual é a relação entre o Next.js e o React? R: O React é uma biblioteca de interface do usuário responsável pela renderização de componentes e pelo gerenciamento de estado; o Next.js é um framework full-stack construído sobre o React, com recursos integrados como roteamento, SSR/SSG/ISR, roteamento de API, otimização de imagens, otimização de fontes e middleware. Você pode pensar da seguinte forma: o React fornece os blocos de construção, enquanto o Next.js fornece a cadeia de ferramentas completa para montar esses blocos em um aplicativo pronto para produção. O React se concentra em “como renderizar a interface do usuário”, enquanto o Next.js aborda “como construir um aplicativo web completo”.
P: Como faço para escolher entre componentes de servidor e componentes de cliente? R: Dê prioridade ao uso de componentes de servidor (a configuração padrão). Adicione
'use client'apenas quando um componente precisar usaruseState,useEffect,onClickou APIs do navegador. Um padrão comum é que um componente de servidor busque dados e os passe para um componente de cliente interno para lidar com as interações. Dessa forma, a recuperação de dados é feita no servidor, enquanto a lógica de interação é executada no navegador — o melhor dos dois mundos. Um erro comum entre iniciantes é adicionar'use client'à página inteira — ele só deve ser aplicado aos menores nós-folha interativos. Se você encontrar um componente que não exija nem interação nem estado, não adicione'use client'.
P: O que tem melhor desempenho, SSR ou SSG? R: O SSG oferece melhor desempenho porque o HTML é gerado durante o processo de compilação, e a CDN serve diretamente os arquivos estáticos sem qualquer sobrecarga de computação do lado do servidor. No entanto, o SSG não é adequado para páginas com conteúdo que muda com frequência. O SSR refaz a renderização a cada solicitação, resultando em maior latência, mas os dados estão sempre atualizados. Um meio-termo é o ISR — ele é tão rápido quanto o SSG na maioria das vezes e regenera o conteúdo sob demanda quando são necessárias atualizações. Em projetos reais, geralmente se usa uma abordagem híbrida: SSG para páginas de marketing, ISR para páginas de produtos e SSR para o painel do usuário.
P: Qual é a diferença entre o App Router e o Pages Router? R: O Pages Router é o método de roteamento usado no Next.js 12 e em versões anteriores. Ele utiliza o diretório
pages/e mapeia rotas com base nos nomes dos arquivos. O App Router é o novo sistema de roteamento introduzido no Next.js 13. Ele utiliza o diretórioapp/e oferece suporte a novos recursos, como layouts aninhados, componentes de servidor e renderização por streaming. Para novos projetos, recomenda-se usar o App Router diretamente; projetos existentes podem ser migrados gradualmente. A estruturagetStaticProps/getServerSidePropsdo Pages Router foi substituída porasync component+fetchno App Router. Os dois sistemas de roteamento podem coexistir e, durante a migração, você pode transferir gradualmente as páginas do Pages Router para o App Router.
P: Os projetos do Next.js precisam ser implantados no Vercel? R: Não necessariamente. Embora o Vercel seja o criador do Next.js e ofereça a experiência de implantação mais integrada (implantação com um clique, escalonamento automático e rede de borda), o Next.js também pode ser implantado em outras plataformas. Você pode usar o
next build && next startpara fazer a implantação em qualquer servidor Node.js ou usar o Docker para implantação em contêineres. O Netlify, o AWS Amplify e o Cloudflare Pages também oferecem suporte à implantação do Next.js. No entanto, se você estiver usando recursos avançados, como ISR e middleware, o Vercel oferece a melhor compatibilidade.
P: E se eu começar com o Pages Router e, mais tarde, quiser migrar para o App Router? R: Os dois sistemas de roteamento podem coexistir no mesmo projeto. Você pode transferir gradualmente as páginas de
pages/paraapp/, verificando cada uma à medida que a transfere — não há necessidade de migrar tudo de uma vez. Durante a migração, observe o seguinte: alteregetStaticPropsparafetchdiretamente no Componente de Servidor; mova o Layout paralayout.tsx, criado no diretórioapp/; e migre a lógica depages/_app.tsxpara a raizlayout.tsx. Recomendamos começar pelas páginas mais simples e lidar com as mais complexas depois de ter adquirido alguma experiência.
P: Como faço para recuperar parâmetros de URL em um Componente de Servidor? R: Os Componentes de Servidor recuperam parâmetros de rota dinâmicos por meio da propriedade
paramse parâmetros de string de consulta por meio da propriedadesearchParams. Por exemplo,function Page({ params, searchParams }: { params: { slug: string }, searchParams: { q: string } }). No entanto, observe que não é possível usaruseRouterouusePathnameem um componente de servidor — esses ganchos estão disponíveis apenas em componentes de cliente.
📖 Resumo
- O Next.js é um framework React full-stack que oferece recursos prontos para uso, como roteamento, otimização de renderização, roteamento de API, otimização de imagens e middleware.
- Quatro modos de renderização: o SSG é o mais rápido e o mais adequado para sites com grande volume de conteúdo; o SSR oferece desempenho em tempo real e é o mais adequado para sites com grande volume de dados; o ISR é um meio-termo (revalidação sob demanda); e o CSR é o mais adequado para aplicativos com grande volume de interações.
- Comparação de desempenho: o SSG apresenta o carregamento mais rápido da primeira página, sem sobrecarregar o servidor; o SSR fornece dados em tempo real, mas sobrecarrega o servidor; o ISR equilibra velocidade e desempenho em tempo real; o CSR é adequado para cenários de interação em segundo plano.
- O App Router utiliza roteamento baseado no sistema de arquivos; os diretórios
app/,page.tsx,layout.tsx,loading.tsx,error.tsxenot-found.tsxcorrespondem, cada um, a uma capacidade de roteamento distinta. - As rotas dinâmicas utilizam a sintaxe
[param], as rotas genéricas utilizam a sintaxe[...param]; as rotas da API utilizam o arquivoroute.ts - Os layouts suportam aninhamento profundo e persistência de estado — a mudança para uma subpágina não interrompe o carregamento do layout atual
- Os componentes de servidor são executados no servidor por padrão e não enviam JavaScript para o navegador; os componentes que exigem interação devem ser declarados como componentes de cliente usando
'use client'. - Arquitetura em camadas: o Componente Servidor funciona como um contêiner de dados, enquanto o Componente Cliente atua como um nó folha interativo, minimizando ao máximo o tamanho do JavaScript no lado do navegador
- As páginas
getStaticProps/getServerSidePropsdo Pages Router no App Router foram substituídas pelo componente async + fetch - Processo de tomada de decisão para escolher um modo de renderização: O conteúdo da página muda com frequência? Os usuários precisam de dados em tempo real? A página requer otimização para mecanismos de busca (SEO)? Leve em consideração todos esses fatores ao escolher entre SSG, ISR, SSR e CSR.
- Processo de migração: CRA ->
create-next-appCriar -> Migrar componentes para o diretórioapp/-> Selecionar o modo de renderização por página -> Extrair o layout -> Adicionar estados de carregamento/erro - Este curso serve de base para os cursos subsequentes sobre recuperação de dados com Next.js, implantação de CI/CD, integração de bibliotecas de componentes e exercícios práticos abrangentes em projetos.
- Lembre-se da lição do Tom: escolha o App Router desde o início para novos projetos, a fim de evitar o custo de uma segunda migração.
📝 Exercícios
-
Use
npx create-next-app@latest my-blogpara criar um novo projeto, certificando-se de que a estrutura de diretórios do App Router seja utilizada. Emapp/, crie três rotas de página:/about,/bloge/blog/[slug]. Cada página deve conter pelo menos um título e uma descrição. Executenpm run deve acesse cada rota (/about,/blog,/blog/test-article) para verificar se os mapeamentos estão corretos. -
Implemente um layout global (
app/layout.tsx) que inclua uma barra de navegação superior (com três links: Página inicial, Blog e Sobre) e um rodapé. Em seguida, crie um layout aninhado para a rota/blog/*a fim de exibir o índice do artigo no lado esquerdo da página de detalhes do artigo. Abra o React DevTools no seu navegador para verificar se a hierarquia de aninhamento do layout está correta e se a barra de navegação e a barra lateral mantêm seu estado sem serem renderizadas novamente ao alternar entre páginas. -
Altere a página inicial do blog para um Componente de Servidor que recupere a lista de artigos diretamente da API JSONPlaceholder (
https://jsonplaceholder.typicode.com/posts) e os exiba. Em seguida, crie um Componente de Cliente para implementar a funcionalidade de pesquisa por palavra-chave e filtragem, permitindo que os usuários pesquisem e filtrem artigos em tempo real no navegador. Certifique-se de que o Componente de Servidor lide com a recuperação de dados, enquanto o Componente de Cliente lida apenas com a lógica de filtragem interativa. Quando isso estiver concluído, adicione umloading.tsx(para exibir uma tela de placeholder) e umerror.tsx(para exibir mensagens de erro e um botão de nova tentativa) para testar a configuração completa do segmento de roteamento. -
Opcional: Implante seu blog no Vercel (gratuito) e observe o processo de revalidação do ISR no console do Vercel — após modificar os dados retornados pela API, verifique se a página é atualizada automaticamente após o término do tempo
revalidate.