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



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.

100%
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.

TSX 📖 Somente leitura
// === 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
42 linhas de lógica (limite de 40, somente leitura)

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

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.

BASH
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
TSX
// 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>&copy; 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

Recursos do Componente Cliente

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.

TSX 📖 Somente leitura
// 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>
  )
}
70 linhas de lógica (limite de 40, somente leitura)

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?

TEXT 📖 Somente leitura
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:


▶ Exemplo 4: Arquivos loading.tsx e error.tsx do App Router

TSX 📖 Somente leitura
// 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
42 linhas de lógica (limite de 40, somente leitura)

▶ Exemplo 5: Layouts e modelos aninhados

TSX 📖 Somente leitura
// 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
44 linhas de lógica (limite de 40, somente leitura)

❓ 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 usar useState, useEffect, onClick ou 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ório app/ 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 estrutura getStaticProps/getServerSideProps do Pages Router foi substituída por async component + fetch no 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 start para 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/ para app/, verificando cada uma à medida que a transfere — não há necessidade de migrar tudo de uma vez. Durante a migração, observe o seguinte: altere getStaticProps para fetch diretamente no Componente de Servidor; mova o Layout para layout.tsx, criado no diretório app/; e migre a lógica de pages/_app.tsx para a raiz layout.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 params e parâmetros de string de consulta por meio da propriedade searchParams. Por exemplo, function Page({ params, searchParams }: { params: { slug: string }, searchParams: { q: string } }). No entanto, observe que não é possível usar useRouter ou usePathname em um componente de servidor — esses ganchos estão disponíveis apenas em componentes de cliente.


📖 Resumo


📝 Exercícios

  1. Use npx create-next-app@latest my-blog para criar um novo projeto, certificando-se de que a estrutura de diretórios do App Router seja utilizada. Em app/, crie três rotas de página: /about, /blog e /blog/[slug]. Cada página deve conter pelo menos um título e uma descrição. Execute npm run dev e acesse cada rota (/about, /blog, /blog/test-article) para verificar se os mapeamentos estão corretos.

  2. 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.

  3. 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 um loading.tsx (para exibir uma tela de placeholder) e um error.tsx (para exibir mensagens de erro e um botão de nova tentativa) para testar a configuração completa do segmento de roteamento.

  4. 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%