Next.js: Roteamento por Sistema de Arquivos do App Router

Última atualização: 2026-08-26

O roteamento por sistema de arquivos é como a forma como os livros são categorizados em uma biblioteca—os nomes dos arquivos determinam o caminho da URL, e a estrutura de diretórios serve como sua tabela de roteamento, sem necessidade de qualquer configuração manual.

1. O Que Você Vai Aprender



2. Uma História Real de um Gerente Técnico

(1) Ponto de Dor: A configuração manual de roteamento é caótica

Bob é o líder técnico em uma empresa de e-commerce, e sua equipe mantém um React SPA de 50 páginas. Toda vez que uma nova página é adicionada, os desenvolvedores precisam configurar manualmente três arquivos:

"Nossa tabela de configuração de roteamento routes.js tem 300 linhas. Na semana passada, Xiao Ming adicionou uma nova página de detalhes de produto, mas esqueceu de registrar path: '/products/:id' na tabela de roteamento, resultando em um erro 404 que passou despercebido por duas horas inteiras após a página entrar no ar."

A equipe de Bob encontrou os seguintes problemas de gerenciamento de roteamento:

Problema Impacto Frequência
Configuração de roteamento ausente 404 após o deploy 1–2 vezes por mês
Tratamento Manual de Rotas Aninhadas Código complexo, hierarquia de rotas confusa Cada nova página
Páginas de Erro 404/500 Dispersas Inconsistente, experiência do usuário ruim Múltiplas páginas
Manutenção Separada de Rotas de API Um conjunto de rotas para o front-end e outro para o back-end Cada API

(2) Soluções para Roteamento por Sistema de Arquivos do Next.js

O nome do arquivo é a URL, e a estrutura de diretórios é a tabela de roteamento—não há mais necessidade de configuração de roteamento react-router-dom.

TEXT 📖 Somente leitura
src/app/
├── page.tsx                    # → /
├── about/
│   └── page.tsx                # → /about
├── products/
│   ├── page.tsx                # → /products
│   └── [id]/
│       ├── page.tsx            # → /products/1, /products/2
│       └── reviews/
│           └── page.tsx        # → /products/1/reviews
└── api/
    └── products/
        └── route.ts            # → /api/products (GET/POST)

(3) Resultados

Dimensão Antes (React Router) Depois (Roteamento por Sistema de Arquivos)
Passos para Adicionar uma Página 3 passos (criar componente + configurar roteamento + importar) 1 passo (criar um arquivo)
Manutenção da Configuração de Roteamento Tabela de roteamento de 300 linhas Zero linhas, determinado automaticamente pelo diretório
Erros 404 ao Lançar 1–2 vezes por mês 0 vezes
Gerenciamento Unificado de Erros 404/500 Importação manual Convenções de arquivo entram em vigor automaticamente


3. Princípios do Roteamento por Sistema de Arquivos

(1) O nome do arquivo é a URL

100%
graph TB
    subgraph "src/app/ Estrutura de Diretórios"
        A[page.tsx] --> B[/]
        C[about/page.tsx] --> D[/about]
        E[products/page.tsx] --> F[/products]
        G[products/promo/page.tsx] --> H[/products/promo]
        I[products/id/page.tsx] --> J[/products/:id]
        K[dashboard/settings/page.tsx] --> L[/dashboard/settings]
    end

    style A fill:#d4edda
    style C fill:#d4edda
    style E fill:#d4edda
Nome do Arquivo URL Correspondente Descrição
app/page.tsx / Início
app/about/page.tsx /about Página Estática
app/blog/page.tsx /blog Lista de Blog
app/blog/[id]/page.tsx /blog/1 Roteamento Dinâmico
app/dashboard/settings/page.tsx /dashboard/settings Aninhado Multi-Nível

(2) Disposições nos Seis Principais Documentos

Nome do Arquivo Finalidade Obrigatório? Comportamento de Renderização
page.tsx Componente de Página (Conteúdo UI) Server Component (padrão)
layout.tsx Contêiner de Layout (Estado Persistente) ✅ Layout raiz Não remontar durante a navegação
loading.tsx Tela de esqueleto de carregamento Opcional Suspense fallback
error.tsx UI de Limite de Erro Opcional Capturar Erros de Componentes Filhos
not-found.tsx Página 404 Opcional notFound() Gatilho
route.ts Endpoint de API Opcional Execução no Lado do Servidor

▶ Exemplo: Criando Sua Primeira Página

Saída:

TEXT 📖 Somente leitura
Diagrama: page.tsx; /; about/page.tsx; /about; products/page.tsx; /products.
TSX
// ============================================
// Criar Página /about
// Documento:src/app/about/page.tsx
// Basta criar um arquivo, Registro Automático de Rota
// ============================================

export default function AboutPage() {
  return (
    <div className="max-w-2xl mx-auto p-8">
      <h1 className="text-3xl font-bold">Sobre o TaskFlow</h1>
      <p className="mt-4 text-gray-600 leading-relaxed">
        TaskFlow é uma plataforma colaborativa de gerenciamento de projetos que ajuda
        equipes a planejar, acompanhar e entregar projetos com eficiência. Construído com
        Next.js 16 e React 19, oferece atualizações em tempo real, colaboração
        perfeita e segurança de nível empresarial.
      </p>
      <div className="mt-8 grid grid-cols-3 gap-4">
        <div className="p-4 bg-blue-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-blue-600">10K+</div>
          <div className="text-sm text-gray-500">Usuários Ativos</div>
        </div>
        <div className="p-4 bg-green-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-green-600">50K+</div>
          <div className="text-sm text-gray-500">Projetos</div>
        </div>
        <div className="p-4 bg-purple-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-purple-600">99.9%</div>
          <div className="text-sm text-gray-500">Tempo de Atividade</div>
        </div>
      </div>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
Conteúdo: Sobre o TaskFlow | TaskFlow é uma plataforma colaborativa | 10K+ | Usuários Ativos

Saída:

TEXT 📖 Somente leitura
Visite http://localhost:3000/about Veja:
Sobre o TaskFlow
TaskFlow é uma plataforma colaborativa de gerenciamento de projetos...
10K+  Usuários Ativos | 50K+  Projetos | 99.9%  Tempo de Atividade


4. Roteamento Dinâmico

(1) Roteamento Dinâmico de Parâmetro Único [id]

100%
graph LR
    A[app/products] --> B[page.tsx → /products]
    A --> C[[id]]
    C --> D[page.tsx → /products/1]
    C --> E[/products/2]

    style B fill:#d4edda
    style D fill:#cce5ff
Modo de Arquivo Exemplo de URL Valor params
app/blog/[slug]/page.tsx /blog/hello-world { slug: 'hello-world' }
app/products/[id]/page.tsx /products/42 { id: '42' }
app/users/[userId]/settings/page.tsx /users/123/settings { userId: '123' }

▶ Exemplo: Página Dinâmica de Detalhes do Produto

Saída:

TEXT 📖 Somente leitura
Diagrama: app/products; page.tsx → /products; [id; page.tsx → /products/1; /products/2.
TSX
// ============================================
// Página de Detalhes do Produto — Roteamento Dinâmico [id]
// Documento:src/app/products/[id]/page.tsx
// ============================================

// No Next.js 16, params é uma Promise (assíncrono)
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await fetch(`https://fakestoreapi.com/products/${id}`);

  return (
    <div className="container mx-auto p-8">
      <div className="flex gap-8">
        <img
          src={product.image}
          alt={product.title}
          className="w-80 h-80 object-contain"
        />
        <div>
          <h1 className="text-2xl font-bold">{product.title}</h1>
          <p className="text-xl text-green-600 font-bold mt-2">
            ${product.price}
          </p>
          <p className="text-sm text-gray-500 mt-1">
            Categoria: {product.category}
          </p>
          <p className="mt-4 text-gray-700">{product.description}</p>
        </div>
      </div>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
O componente do servidor busca e renderiza dados.

Saída:

TEXT 📖 Somente leitura
Visite /products/1 Veja:
Fjallraven - Foldsack No. 1 Backpack
$109.95
Categoria: men's clothing
Sua mochila perfeita para viagens de um dia e caminhadas...

(2) Rotas Catch-all [...slug]

100%
graph TB
    A[app/docs] --> B[[...slug]]
    B --> C[page.tsx]
    C --> D[/docs/getting-started]
    C --> E[/docs/guides/installation]
    C --> F[/docs/api/authentication/overview]

    style C fill:#cce5ff
Modo de Arquivo URL params.slug
app/docs/[...slug]/page.tsx /docs 404 (Nível 1 é obrigatório)
app/docs/[...slug]/page.tsx /docs/getting-started ['getting-started']
app/docs/[...slug]/page.tsx /docs/guides/installation ['guides', 'installation']
app/docs/[...slug]/page.tsx /docs/a/b/c ['a', 'b', 'c']

▶ Exemplo: Página de Documentação Catch-all

Saída:

TEXT 📖 Somente leitura
Diagrama: app/docs; [...slug; page.tsx; /docs/getting-started; /docs/guides/installation; /docs/api/authentication/overview.
TSX
// ============================================
// Catch-all — Páginas de Documentos Multi-Nível
// Documento:src/app/docs/[...slug]/page.tsx
// ============================================

export default async function DocsPage({
  params,
}: {
  params: Promise<{ slug: string[] }>;
}) {
  const { slug } = await params;

  return (
    <div className="container mx-auto p-8">
      <nav className="text-sm text-gray-500 mb-4">
        Início / Docs / {slug.join(" / ")}
      </nav>
      <h1 className="text-3xl font-bold">Docs: {slug.join(" > ")}</h1>
      <div className="mt-8 p-6 bg-yellow-50 border border-yellow-200 rounded-lg">
        <p className="font-medium">Você está visualizando documentação para:</p>
        <ul className="mt-2 list-disc list-inside">
          {slug.map((segment, index) => (
            <li key={index}>
              Nível {index + 1}: <code className="bg-gray-100 px-2 py-0.5 rounded">{segment}</code>
            </li>
          ))}
        </ul>
      </div>
      <p className="mt-4 text-gray-600">
        Profundidade total: {slug.length} níveis
      </p>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
DocsPage renderiza sua UI.

Saída:

TEXT 📖 Somente leitura
Visite /docs/getting-started/installation Veja:
Início / Docs / getting-started / installation
Docs: getting-started > installation

Você está visualizando documentação para:
  - Nível 1: getting-started
  - Nível 2: installation
Profundidade total: 2 níveis

(3) Catch-all Opcional [[...catchAll]]

100%
graph TB
    A[app/categories] --> B[[[...catchAll]]]
    B --> C[page.tsx]
    C --> D[/categories]
    C --> E[/categories/electronics]
    C --> F[/categories/electronics/phones]

    style C fill:#d4edda
    style D fill:#d4edda
Modo de Arquivo URL params.catchAll
app/categories/[[...catchAll]]/page.tsx /categories undefined
app/categories/[[...catchAll]]/page.tsx /categories/electronics ['electronics']
app/categories/[[...catchAll]]/page.tsx /categories/electronics/phones ['electronics', 'phones']

▶ Exemplo: Página de Categoria Catch-all Opcional

Saída:

TEXT 📖 Somente leitura
Diagrama: app/categories; [[...catchAll; page.tsx; /categories; /categories/electronics; /categories/electronics/phones.
TSX
// ============================================
// Catch-all Opcional — Página de Navegação de Categoria
// Documento:src/app/categories/[[...categories]]/page.tsx
// Tanto /categories quanto /categories/electronics entram em vigor
// ============================================

export default async function CategoriesPage({
  params,
}: {
  params: Promise<{ categories?: string[] }>;
}) {
  const { categories } = await params;

  const allProducts = await fetch("https://fakestoreapi.com/products");
  const categoriesList = [...new Set(allProducts.map(p => p.category))];

  const filteredProducts = categories
    ? allProducts.filter(p => p.category === categories[0])
    : allProducts;

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">
        {categories ? `Categoria: ${categories[0]}` : "Todas as Categorias"}
      </h1>

      {!categories && (
        <div className="flex gap-2 mb-8 flex-wrap">
          {categoriesList.map(cat => (
            <a
              key={cat}
              href={`/categories/${encodeURIComponent(cat)}`}
              className="px-4 py-2 bg-gray-100 rounded-full hover:bg-blue-100"
            >
              {cat}
            </a>
          ))}
        </div>
      )}

      <div className="grid grid-cols-4 gap-6">
        {filteredProducts.slice(0, 8).map(p => (
          <div key={p.id} className="border rounded-lg p-4 hover:shadow-lg">
            <img src={p.image} alt={p.title} className="h-40 mx-auto" />
            <p className="mt-2 font-medium text-sm truncate">{p.title}</p>
            <p className="text-green-600 font-bold">${p.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
A busca de dados do lado do servidor renderiza a lista de itens.

Saída:

TEXT 📖 Somente leitura
Visite /categories Veja:
Botões de Todas as Categorias(electronics, jewelery, men's clothing, women's clothing)

Visite /categories/electronics Veja:
Categoria: electronics
Mostrar apenas produtos eletrônicos(8 Cards de Produto)


5. loading.tsx e error.tsx

(1) loading.tsx — Carregar a tela de esqueleto

100%
graph LR
    A[Navegação do Usuário] --> B[loading.tsx<br/>Exibição de Wireframe]
    B --> C[page.tsx<br/>Dados Prontos]
    C --> D[Página Completa]

    style B fill:#f8d7da
    style C fill:#d4edda

▶ Exemplo: Carregando uma tela de esqueleto

Saída:

TEXT 📖 Somente leitura
Diagrama: Navegação do Usuário; loading.tsx Exibição de Wireframe; page.tsx Dados Prontos; Página Completa.
TSX
// ============================================
// Carregando a tela de esqueleto — Exibida quando os dados da página estão sendo recuperados
// Documento:src/app/products/loading.tsx
// ============================================

export default function ProductsLoading() {
  return (
    <div className="container mx-auto p-8">
      <div className="h-8 w-48 bg-gray-200 rounded animate-pulse mb-6" />
      <div className="grid grid-cols-4 gap-6">
        {Array.from({ length: 8 }).map((_, i) => (
          <div key={i} className="border rounded-lg p-4">
            <div className="h-40 bg-gray-200 rounded animate-pulse" />
            <div className="h-4 bg-gray-200 rounded mt-2 animate-pulse" />
            <div className="h-4 w-16 bg-gray-200 rounded mt-2 animate-pulse" />
          </div>
        ))}
      </div>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
Renderiza uma lista de itens usando .map().

Saída:

TEXT 📖 Somente leitura
Ao visitar /products, exibe até que o carregamento de dados esteja completo:
8 Cartões de placeholder cinza(Animação com Pulsos)
Após o carregamento dos dados ser concluído, O conteúdo de placeholder é automaticamente substituído pelo conteúdo real
Sem cintilação, Sem tela branca, Transição Suave

(2) error.tsx — Limite de Erro

▶ Exemplo: Página de Erro

Saída:

TEXT 📖 Somente leitura
A página renderiza como descrito acima, com a UI atualizando baseada no comportamento descrito.
TSX
// ============================================
// Limite de Erro — Capturando Erros de Componentes Filhos
// Documento:src/app/products/error.tsx
// ============================================

'use client';

export default function ProductsError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="container mx-auto p-8 text-center">
      <div className="max-w-md mx-auto">
        <h2 className="text-2xl font-bold text-red-600 mb-4">
          Algo deu errado!
        </h2>
        <p className="text-gray-600 mb-6">
          {error.message || "Falha ao carregar produtos. Por favor, tente novamente."}
        </p>
        <button
          onClick={reset}
          className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
        >
          Tentar Novamente
        </button>
      </div>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
Conteúdo: Algo deu errado! | Tentar Novamente

Saída:

TEXT 📖 Somente leitura
Quando a recuperação de dados da página do produto falha, Mostra:
Algo deu errado!
Falha ao carregar produtos. Por favor, tente novamente.
[Try Again] botão → Clique para tentar novamente automaticamente

(3) not-found.tsx — Página 404


TEXT 📖 Somente leitura

---
## 6. Route Handlers (Rotas de API)

### (1) Uso Básico de route.ts

```mermaid
graph LR
    A[Requisição do Cliente] --> B[route.ts]
    B --> C[GET /api/products]
    B --> D[POST /api/products]
    B --> E[PUT /api/products/:id]
    B --> F[DELETE /api/products/:id]

    style B fill:#cce5ff
Método HTTP Função Exportada Finalidade
GET export async function GET() Ler Dados
POST export async function POST() Criar Dados
PUT export async function PUT() Atualizar Dados
DELETE export async function DELETE() Excluir Dados
PATCH export async function PATCH() Atualização Parcial

TEXT 📖 Somente leitura

---
## 7. Exemplo Completo: Sistema de Navegação de Produtos E-commerce

```tsx
// ============================================
// Exemplo Abrangente: Sistema de Navegação de Produtos E-commerce
// Páginas Cobertas, Layout, Loading, Error, Roteamento de API
// ============================================

// src/app/layout.tsx — Layout Raiz
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="pt-BR">
      <body className="bg-gray-50">
        <nav className="bg-white shadow-sm p-4">
          <a href="/" className="text-xl font-bold text-blue-600">
            ShopHub
          </a>
        </nav>
        <main>{children}</main>
      </body>
    </html>
  );
}

// src/app/page.tsx — Início
export default function HomePage() {
  return (
    <div className="container mx-auto p-8 text-center">
      <h1 className="text-4xl font-bold">Bem-vindo ao ShopHub</h1>
      <p className="mt-4 text-gray-600">
        Navegue pela nossa coleção de produtos incríveis.
      </p>
      <a
        href="/products"
        className="inline-block mt-6 px-8 py-3 bg-blue-600 text-white rounded-lg"
      >
        Navegar Produtos
      </a>
    </div>
  );
}

// src/app/products/page.tsx — Lista de Produtos
export default async function ProductsPage() {
  const products = await fetch(
    "https://fakestoreapi.com/products"
  );

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">Todos os Produtos</h1>
      <div className="grid grid-cols-4 gap-6">
        {products.map(p => (
          <a
            key={p.id}
            href={`/products/${p.id}`}
            className="border rounded-lg p-4 bg-white hover:shadow-lg"
          >
            <img
              src={p.image}
              alt={p.title}
              className="h-40 mx-auto"
            />
            <p className="mt-2 font-medium text-sm">{p.title}</p>
            <p className="text-green-600 font-bold mt-1">${p.price}</p>
          </a>
        ))}
      </div>
    </div>
  );
}

// src/app/products/[id]/page.tsx — Detalhes do Produto
export default async function ProductDetailPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await fetch(
    `https://fakestoreapi.com/products/${id}`
  );

  if (!product) {
    notFound();
  }

  return (
    <div className="container mx-auto p-8">
      <a
        href="/products"
        className="text-blue-600 hover:underline mb-4 inline-block"
      >
        &larr; Voltar para produtos
      </a>
      <div className="flex gap-8 bg-white p-8 rounded-lg shadow">
        <img
          src={product.image}
          alt={product.title}
          className="w-96 h-96 object-contain"
        />
        <div>
          <h1 className="text-3xl font-bold">{product.title}</h1>
          <p className="text-2xl text-green-600 font-bold mt-4">
            ${product.price}
          </p>
          <p className="mt-6 text-gray-700 leading-relaxed">
            {product.description}
          </p>
          <button className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-lg">
            Adicionar ao Carrinho
          </button>
        </div>
      </div>
    </div>
  );
}

// src/app/products/loading.tsx — Carregando a tela de esqueleto
export default function ProductsLoading() {
  return (
    <div className="container mx-auto p-8">
      <div className="h-8 w-48 bg-gray-200 rounded animate-pulse mb-6" />
      <div className="grid grid-cols-4 gap-6">
        {Array.from({ length: 8 }).map((_, i) => (
          <div key={i} className="border rounded-lg p-4 bg-white">
            <div className="h-40 bg-gray-200 rounded animate-pulse" />
            <div className="h-4 bg-gray-200 rounded mt-2 animate-pulse" />
            <div className="h-4 w-16 bg-gray-200 rounded mt-2 animate-pulse" />
          </div>
        ))}
      </div>
    </div>
  );
}

// src/app/products/error.tsx — Limite de Erro
'use client';
export default function ProductsError({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="container mx-auto p-8 text-center">
      <h2 className="text-2xl font-bold text-red-600">
        Falha ao carregar produtos
      </h2>
      <button
        onClick={reset}
        className="mt-4 px-6 py-2 bg-blue-600 text-white rounded-lg"
      >
        Tentar Novamente
      </button>
    </div>
  );
}

Saída Esperada:

TEXT 📖 Somente leitura
Início(/):
  Bem-vindo ao ShopHub → [Navegar Produtos]

Lista de Produtos(/products):
  Carregando... → 8 Telas de esqueleto cinzas
  Carregamento completo → Grade de Cards de Produto(Inclui imagens, Título, Preço)

Detalhes do Produto(/products/1):
  Imagens do Produto (esquerda) + Título/Preço/Descrição/Adicionar ao Carrinho (direita)
  ← Voltar para produtos Link Voltar para Lista

Status de Erro:
  Falha ao carregar produtos
  [Tentar Novamente] botão

Status 404:
  Automaticamente do raiz not-found.tsx Processamento

❓ Perguntas Frequentes

P: Posso usar .jsx em vez de .tsx para page.tsx? R: Sim. Se TypeScript não estiver habilitado no seu projeto, você pode usar a extensão .jsx. No entanto, este tutorial recomenda TypeScript, então todos os exemplos de código usam .tsx.

P: Por que params são Promises no Next.js 16? R: A partir do Next.js 15, params, searchParams e variáveis semelhantes foram alteradas para Promises para suportar geração assíncrona de páginas. Se você usá-los diretamente params.id, encontrará um erro TypeScript; você deve await params para recuperar seus valores.

P: Qual é a relação entre loading.tsx e Suspense? R: loading.tsx é um fallback Suspense no nível da página. Next.js automaticamente envolve loading.tsx em &lt;Suspense&gt;. Se você precisar de um estado de carregamento no nível do componente, deve usar manualmente &lt;Suspense fallback={...}&gt;.

P: Por que error.tsx precisa de use client? R: error.tsx deve ser um Client Component porque precisa lidar com interações (como o botão de reset). Server Components não podem conter manipuladores de eventos ou hooks, então a diretiva 'use client' é necessária.

P: route.ts e page.tsx podem coexistir? R: Não. Sob o mesmo segmento de rota, page.tsx e route.ts são mutuamente exclusivos. Um diretório pode conter apenas um tipo de manipulador de rota—seja uma página (page.tsx) ou uma API (route.ts).

P: Qual é exatamente a diferença entre [...slug] e [[...catchAll]]? R: [...slug] requer pelo menos um segmento de caminho (/docs/a corresponde; /docs não corresponde e retorna 404). [[...catchAll]] permite zero segmentos de caminho (tanto /categories quanto /categories/a correspondem), tornando-o adequado para cenários de "navegação de categoria opcional".


📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie uma página chamada app/blog/[slug]/page.tsx no projeto para que quando /blog/hello-world for acessado, o console exiba slug: hello-world.

  2. Exercício Avançado (⭐⭐): Crie app/api/todos/route.ts para implementar uma API TODO simples (GET retorna uma lista, POST cria um TODO), e verifique acessando-o em um navegador.

  3. Desafio (⭐⭐⭐): Crie duas implementações, app/docs/[...slug]/page.tsx e app/docs/[[...slug]]/page.tsx, para testar o comportamento de acesso de /docs, e use uma tabela para comparar as diferenças entre as duas.

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%