Next.js 13 Avançado: App Router, Server Components e Otimização de Performance
Next.js 13 Avançado: App Router, Server Components e Otimização de Performance
Neste post didático e prático, vamos explorar os recursos avançados introduzidos no Next.js 13 – como o App Router, React Server Components e Middlewares – e como usá-los para construir aplicações web escaláveis e de alta performance. Você vai aprender a nova estrutura de rotas do Next.js, a diferença entre componentes no servidor e no cliente, além de estratégias de otimização que podem acelerar sua aplicação. Preparado? Vamos lá!
Introdução
O Next.js 13 trouxe diversas inovações para criar aplicações React mais eficientes. Entre elas, o App Router revoluciona a forma de definir rotas usando o diretório /app, enquanto os Server Components permitem renderizar partes da UI no servidor, reduzindo o JavaScript enviado para o cliente. Também existem novos recursos de performance – como otimizações de carregamento de imagens e links – e suporte nativo a middlewares, que executam lógica antes da renderização das páginas.
Ao longo deste artigo, você verá como estruturar um projeto com o novo App Router, como usar componentes do servidor e do cliente, como proteger rotas com middleware, e quais práticas e componentes internos do Next.js podem melhorar o desempenho do seu app. Todas as explicações serão claras, acompanhadas de analogias e exemplos de código em JavaScript/React para facilitar o entendimento. Vamos começar entendendo as novidades do Next.js 13 e o que mudou em relação às versões anteriores.
1. Novidades do Next.js 13 e o App Router
O Next.js 13 introduziu um novo App Router, uma evolução do tradicional Pages Router. Com o App Router, você passa a usar o diretório /app em vez de /pages para definir suas rotas. Essa mudança permite aproveitar recursos modernos do React, como React Server Components e layouts aninhados, de forma nativa.
1.1 Estrutura de Diretórios no App Router
No App Router, a estrutura de pastas mapeia diretamente as rotas da aplicação. Por exemplo, imagine um app com páginas de início, sobre e produtos:
/app
/layout.js // Layout principal (aplica-se a todas as rotas filhas)
/page.js // Página principal ("/")
/sobre
/page.js // Página "/sobre"
/produtos
/layout.js // Layout para rotas de produtos
/page.js // Página "/produtos"
/[id]
/page.js // Página dinâmica "/produtos/123"
/login
/page.js // Página "/login"
- Cada arquivo
page.js(ou.tsx) dentro de uma pasta representa um endpoint de rota. - O arquivo
layout.jsdefine um layout compartilhado para todas as páginas dentro daquela pasta, envolvendo-as com uma estrutura comum (cabeçalho, rodapé, etc.). - A pasta
/produtos/[id]/page.jsmostra como criar uma rota dinâmica: o segmento[id]indica um parâmetro de rota. No exemplo, acessando/produtos/123, o Next.js entrega o conteúdo depage.jsdentro dessa pasta comparams.id = "123".
1.2 Rotas Estáticas vs Dinâmicas
Com o App Router, existem diferentes tipos de rotas:
- Rota Estática: caminho fixo. Exemplo:
/sobrecorresponde a/app/sobre/page.js. - Rota Dinâmica: com colchetes. Exemplo:
/produtos/[id]permite capturar o parâmetroid. A página recebeparamscom valores. - Rota Catch-all: usando colchetes com três pontos. Exemplo:
/blog/[...slug]captura grupos de caminho. Se o usuário acessar/blog/react/13/novidades, oslugseria["react", "13", "novidades"].
1.3 Layouts e Roteamento Aninhado
Um dos maiores benefícios do App Router é suporte nativo a layouts aninhados. Pense em layouts como molduras que envolvem as páginas. Cada pasta pode ter seu layout.js:
// app/layout.js export default function Layout({ children }) { return ( <html> <body> <header>Meu App</header> <main>{children}</main> <footer>© 2024 Minha Empresa</footer> </body> </html> ); }
Esse layout principal será usado em todas as rotas (porque está no nível raiz do /app). Já o app/produtos/layout.js poderia definir um layout específico para todas as páginas de /produtos:
// app/produtos/layout.js export default function ProdutosLayout({ children }) { return ( <div> <nav>Navegação de produtos</nav> {children} {/* Conteúdo das páginas filhas (como /produtos e /produtos/[id]) */} </div> ); }
Desse modo, a página /produtos/page.js e qualquer /produtos/[id]/page.js irão aparecer dentro do contexto desse layout secundário. Você pode aninhar quantos níveis precisar — layouts não se re-renderizam ao mudar de página, o que aumenta a performance e evita recarregamentos desnecessários de elementos comuns.
1.4 Estados de Carregamento e de Erro
Outra novidade útil: o Next.js 13 permite que você crie componentes especiais dentro do App Router para tratar carregamento (loading.js) e erro (error.js) de uma rota.
Por exemplo, para exibir uma mensagem enquanto os dados de /produtos/[id] carregam, basta criar app/produtos/[id]/loading.js:
// app/produtos/[id]/loading.js export default function LoadingProduct() { return <p>Carregando detalhes do produto...</p>; }
Se algum erro ocorrer durante a renderização dessa rota, um componente app/produtos/[id]/error.js pode mostrar uma mensagem amigável. Essas funcionalidades utilizam o recurso Suspense do React e permitem criar experiências de usuário mais suaves.
Resumo: O App Router do Next 13 usa o diretório
/apppara definir rotas via arquivos (page.js/tsx) e layouts aninhados (layout.js). Rotas dinâmicas usam colchetes e é possível ter componentes de loading e error específicos. Isso fornece maior controle estrutural e otimiza a renderização.
2. React Server Components (Componentes do Servidor) no Next.js 13
Uma das grandes novidades trazidas pelo React e incorporada no Next.js 13 são os React Server Components. Em termos simples, um Server Component é um componente React que é renderizado no servidor e enviado ao cliente como HTML estático, sem código JavaScript extra. Isso reduz drasticamente o peso do JavaScript que roda no navegador e melhora a performance de carregamento.
Analogia: Imagine que, ao invés de enviar a receita completa de um prato ao cliente (cada passo, utensílio, etc.), o servidor já envia o prato pronto no prato. O cliente recebe apenas a versão final (instruções mínimas para exibir) e não precisa do chef (JavaScript) em execução nele.
2.1 Como identificar um Server Component
Por padrão, qualquer arquivo de rota (page.js/page.tsx) no App Router do Next.js é um Server Component. Você não precisa escrever nada especial; basta não incluir a diretiva 'use client' no topo do arquivo.
Exemplo de Server Component para uma página /produtos que busca dados de um API:
// app/produtos/page.js export default async function ProductsPage() { // Executa no servidor: busca dados do banco ou API const res = await fetch('https://api.exemplo.com/produtos'); const produtos = await res.json(); return ( <div> <h1>Lista de Produtos</h1> <ul> {produtos.map(produto => ( <li key={produto.id}> {produto.nome} - {produto.preco} </li> ))} </ul> </div> ); }
Neste exemplo, ProductsPage é um componente do servidor. Ele pode usar await fetch() diretamente. O Next.js sabe executá-lo no servidor e enviar ao cliente apenas o HTML resultante.
2.2 Vantagens dos Server Components
Os Server Components trazem vários benefícios importantes:
- Menor bundle no cliente: como grande parte da lógica (especialmente fetch de dados) fica no servidor, o pacote (bundle) JavaScript enviado ao navegador é muito menor.
- Melhor SEO e tempo de primeiro carregamento (TTFB): conteúdo já renderizado pelo servidor aparece mais rápido para o usuário e para ferramentas de busca.
- Segurança: chaves de API e lógica sensível ficam apenas no servidor, não expostas ao cliente.
- Streaming (React 18): o Next.js usa streaming para enviar o HTML assim que partes ficam prontas, garantindo telas iniciais rápidas.
- Integração com Suspense: combinado com componentes de carregamento (
loading.js), podemos criar placeholders enquanto dados são buscados.
Isso não quer dizer que tudo deve ser servido do servidor. Há casos em que precisamos de interatividade no cliente (ex: estados, eventos). Para esses casos, usamos Client Components.
2.3 Componentes de Cliente (Client Components)
Quando você precisa de estado React, efeitos colaterais ou acessos ao navegador (DOM), precisa declarar explicitamente um componente como cliente. Basta adicionar a linha 'use client' no topo do arquivo:
// app/contadores/page.js 'use client'; import { useState } from 'react'; export default function Contador() { const [count, setCount] = useState(0); return ( <div> <p>Contador: {count}</p> <button onClick={() => setCount(count + 1)}> Incrementar </button> </div> ); }
Este exemplo de contador é um Client Component, pois usa useState e eventos de click. O Next.js vai empacotá-lo para rodar no navegador. Note que mesmo nos Client Components podemos aproveitar funcionalidades do Next.js (como a importação dinâmica ou o componente Image etc.), mas todo CSS Modules e bibliotecas que manipulam o DOM só funcionam aqui.
Resumo: Server Components (sem
'use client') são padrão no App Router e rodam no servidor. Client Components (com'use client') rodam no navegador. Use Server Components para lógica pesada e fetch de dados, e Client Components para interatividade.
3. Middlewares no Next.js 13
Os Middlewares em Next.js são funções especiais que rodem antes da entrega das páginas, permitindo interceptar requisições, reescrevê-las, redirecionar usuários ou definir cabeçalhos. A grande mudança no Next.js 13 é que os middlewares agora também convivem muito bem com o App Router. São ideais, por exemplo, para autenticação, gerenciamento de A/B tests ou análise de tráfego.
Para criar um middleware, basta colocar um arquivo middleware.ts (ou .js) no nível raiz do seu projeto:
// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // Exemplo: proteção de rota /dashboard if (request.nextUrl.pathname.startsWith('/dashboard')) { const token = request.cookies.get('authToken')?.value; if (!token) { // Redireciona para /login se não estiver autenticado const url = new URL('/login', request.url); return NextResponse.redirect(url); } } // Continua a requisição normalmente return NextResponse.next(); } export const config = { matcher: ['/dashboard/:path*'], // Aplica a qualquer rota que comece com /dashboard };
Neste exemplo, toda vez que um usuário tenta acessar algo em /dashboard, o middleware verifica se existe um cookie authToken. Se não existir, a requisição é interrompida e é redirecionado para /login. Caso contrário, continua normalmente com NextResponse.next().
Pontos importantes sobre Middlewares:
- Executam no Edge Runtime: O middleware roda em um ambiente de alta performance semelhante ao Edge. Isso significa baixa latência e suporte a APIs do Web Standard, mas não a módulos do Node.js puro (como sistemas de arquivos).
- Uso comum: autenticação, redirects, reescritas (
NextResponse.rewrite), logs ou cabeçalhos personalizados. - Configuração de Matchers: com
export const config.matcher, você define quais rotas devem passar pelo middleware (por padrão é tudo). - Retorno de resposta: além de
redirectenext, você pode usarNextResponse.rewrite('/outra-rota')para reescrever a rota internamente sem mudar a URL do usuário, por exemplo.
Exemplo prático – Redirecionamento global:
// middleware.ts import { NextResponse } from 'next/server'; export function middleware(request) { // Bloqueia acesso a /beta para usuários não autorizados if (request.nextUrl.pathname.startsWith('/beta')) { const isMember = request.cookies.get('membroPremium')?.value === 'true'; if (!isMember) { return NextResponse.redirect(new URL('/upgrade', request.url)); } } return NextResponse.next(); } export const config = { // Aplica a todas as páginas, exceto arquivos estáticos matcher: [ '/((?!_next/static|_next/image|favicon.ico).*)' ], };
Nesse caso, qualquer rota que comece com /beta vai verificar se existe o cookie membroPremium. Caso contrário, redireciona para /upgrade. O matcher configurado aplica o middleware a todas as rotas (matcher de negação exclusão de arquivos estáticos do Next.js).
4. Otimização de Performance no Next.js 13
Além das novas arquiteturas de rota e componente, o Next.js possui várias otimizações automáticas e recursos para você melhorar ainda mais o desempenho da aplicação. Vamos ver alguns deles:
4.1 Pré-carregamento com Link e Script
O Next.js fornece componentes especiais que já cuidam de performance:
-
next/link: componente para navegação interna. Ele automaticamente prefaz o carregamento das páginas vinculadas nos links que estão próximos de aparecer na tela, resultando em transições de página muito rápidas. Exemplo:import Link from 'next/link'; export default function NavBar() { return ( <nav> <Link href="/">Início</Link> <Link href="/sobre">Sobre nós</Link> </nav> ); }Aqui, ao carregar a página atual, o Next.js pode pré-carregar em segundo plano o JavaScript e os dados necessários de "/" e "/sobre", fazendo com que clicar no link seja quase imediato.
-
next/script: controle de scripts de terceiros. Você pode definir se o script carrega de forma síncrona, assíncrona ou só após a página carregar (strategy="lazyOnload"). Isso ajuda a evitar bloqueio da renderização:import Script from 'next/script'; export default function HomePage() { return ( <> {/* Carrega após a página estar pronta */} <Script src="https://example.com/analytics.js" strategy="lazyOnload" /> <h1>Bem-vindo!</h1> </> ); }
4.2 Componente de Imagens
-
next/image: otimiza automaticamente imagens em suas páginas. Ele faz redimensionamento, compressão e lazy loading conforme o dispositivo do usuário. Basta usar o componente:import Image from 'next/image'; export default function Avatar() { return ( <Image src="/perfil.jpg" width={200} height={200} alt="Foto do Perfil" /> ); }Isso gera imagens otimizadas e garante que não seja feito download de tamanhos excessivos. O Next.js até consegue gerar múltiplos tamanhos para telas diferentes (webp por exemplo).
4.3 Renderização Estática e Revalidação
Mesmo com o App Router, o Next.js permite pré-renderizar páginas para tornar seu app mais rápido:
-
Static Generation (SSG): páginas que usam apenas dados estáticos (ou dados que mudam raramente) podem ser pré-renderizadas no build. Assim, o usuário recebe o HTML já pronto, sem esperar carregamento de API.
-
Incremental Static Regeneration (ISR): você pode dizer ao Next.js para revalidar a página em segundo plano a cada X segundos. No App Router, isso se faz exportando uma constante
revalidate. Exemplo:// app/blog/page.js export const revalidate = 60; // revalida a cada 60 segundos export default async function BlogPage() { const res = await fetch('https://api.exemplo.com/posts'); const posts = await res.json(); return ( <div> {posts.map(post => ( <Article key={post.id} {...post} /> ))} </div> ); }Com isso, a primeira requisição gera uma página estática. A cada 60 segundos, uma nova versão é gerada no servidor, e os usuários recebem conteúdo atualizado sem rebuild manual.
-
Cache de Fetch: o
fetchusado em Server Components já tem caching inteligente. Você pode configurar (como no exemplo acima) ou deixar o padrão (que écache: "force-cache", ou definindonext: { revalidate: X }nas opções dofetchpara personalizar). Isso evita chamadas desnecessárias à API.
4.4 Splitting de Código e Bundles Menores
O Next.js realiza automaticamente code splitting baseado em rotas. Cada página carrega só o JavaScript que ela precisa. Além disso, com Server Components a quantidade de JS no cliente diminui. Algumas práticas recomendadas ajudam:
- Evite bibliotecas monolíticas no Client: se uma biblioteca for usada só no servidor, verifique se ela não acaba sendo empacotada no cliente. Por exemplo, use
importdinâmico (next/dynamic) ou coloque a lógica no server component. - Use o modo de produção: na hora de build, o Next.js minifica e treeshaka seu código, reduzindo ainda mais o tamanho do bundle.
- Imagem e Vídeo responsivos: use o componente
Imageou serviços de CDN de imagem para servir tamanhos adequados.
4.5 Outras Boas Práticas
- Fontes Otimizadas: o Next.js 13 oferece suporte a
next/font(inemplo, Google Fonts). Isso embute estilos de fonte mais eficientes. - Análise de Performance: use o Lighthouse ou o perfilador do React para identificar gargalos.
- Procure por “Web Vitals”: métricas de Core Web Vitals (LCP, FID, CLS) ajudam a medir experiência real. O Next já se preocupa com elas, mas otimizações próprias (minimizar time-to-first-byte, etc.) são sempre bem-vindas.
Resumo: Aproveite os componentes Next/Link, Next/Image e Next/Script para pré-carregamento e otimização automática de recursos. Use geração estática e revalidação (ISR) para balancear frescor de dados e velocidade. Mantenha bundles pequenos e cuide de métricas de performance para garantir uma aplicação rápida e eficiente.
Conclusão
O Next.js 13 traz um conjunto poderoso de ferramentas para desenvolvedores que desejam criar aplicações web modernas e rápidas. Neste post vimos como:
- O App Router substitui (ou complementa) o antigo Pages Router, usando uma estrutura de pastas hierárquica em
/app, compatível com layouts aninhados, páginas dinâmicas e recursos comoloading.js/error.jspara melhor UX durante o carregamento de dados. - Os Server Components permitem renderizar conteúdo no servidor, reduzindo o JavaScript enviado ao cliente e melhorando o desempenho. Podemos combiná-los com Client Components (via
'use client') para interatividade onde necessário. - Os Middlewares oferecem um ponto de interceptação em cada requisição, rodando no Edge para permitir autenticação, redirecionamentos e outras lógicas antes de a página ser renderizada definitivamente.
- Existem várias otimizações de performance embutidas: desde o pré-carregamento automático de rotas pelo componente
Link, até o redimensionamento de imagens peloImage, passando por estratégias de cache e renderização estática com revalidação. Seguindo essas práticas, sua aplicação Next.js ficará mais rápida e escalável.
Para continuar aprendendo, explore a documentação oficial do Next.js 13 e pratique criando pequenos projetos com cada recurso novo. Tente migrar uma rota com o novo App Router, use um Server Component para buscar dados, implemente um middleware simples de autenticação e monitore o impacto no desempenho usando ferramentas como Lighthouse. Com o tempo, você ganhará familiaridade com esses padrões avançados e colherá os benefícios em aplicações reais.
Próximos passos recomendados: inscreva-se no release notes do Next.js, acompanhe a comunidade (blogs, fóruns) para novidades (como o Turbopack no futuro!), e continue iterando seu projeto atual aplicando essas técnicas. Boa codificação!
Quer aprender mais sobre Programação?
Você acabou de ganhar 1 hora com a minha equipe para uma call exclusiva! Vamos entender o seu momento e te mostrar o caminho para se tornar um programador de sucesso. Clique no botão abaixo e agende agora mesmo!