Next.js: Layouts e Modelos (Templates)

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

Um sistema de layout é como os andares de um prédio — cada andar (layout) possui corredores e instalações compartilhadas, enquanto cada sala (página) pode ter uma decoração diferente, e o estado dos usuários do espaço é preservado conforme eles se movem entre os andares.

1. O Que Você Vai Aprender



2. Uma História Real de uma Desenvolvedora Full-Stack

(1) Ponto de Dor: Ter que escrever repetidamente a barra de navegação e a barra lateral para cada página

Ao desenvolver o painel administrativo do TaskFlow, Alice encontrou um problema de duplicação de código de layout:

"Nossa equipe tem cinco desenvolvedores, cada um trabalhando em páginas diferentes. Todos precisam importar manualmente a barra de navegação e a barra lateral em seus próprios arquivos page.tsx. Na semana passada, Charlie esqueceu de adicionar a barra lateral na página recém-criada settings/page.tsx, então quando um usuário clicou na página de configurações, o menu desapareceu de repente, e eles pensaram que a navegação estava quebrada."

Duplicação de Código:

Problema Impacto Páginas Afetadas
Importações duplicadas da barra de navegação Deve ser incluída manualmente em cada página 15 páginas
Estado da Barra Lateral Não é Persistente O estado selecionado na barra lateral é perdido após navegar Todas as Páginas
Navegação exibida na página de login/cadastro Não deveria ser exibida; requer verificações condicionais adicionais 3 páginas
Transmissão complexa de dados do usuário Os dados do usuário precisam ser buscados em cada página 12 páginas

(2) Soluções com o Sistema de Layout do Next.js

Use layouts aninhados e Grupos de Rotas para isolar layouts; defina uma vez para aplicação global.

TEXT 📖 Somente leitura
src/app/
├── layout.tsx                  # Layout Raiz (html/body/Fonte)
├── page.tsx                    # Página Inicial
├── (auth)/
│   ├── layout.tsx              # Layout de Login/Cadastro (Sem barra lateral)
│   ├── login/page.tsx
│   └── register/page.tsx
└── (dashboard)/
    ├── layout.tsx              # Layout do Painel Admin (Navegação+Barra Lateral)
    ├── page.tsx                # Dashboard
    ├── projects/page.tsx       # Lista de Projetos
    └── settings/page.tsx       # Página de Configurações

(3) Ganhos

Dimensão Antes (importação manual) Depois (sistema de layout)
Referências da Barra de Navegação 15 linhas de import em 15 páginas 1 arquivo layout.tsx
Estado de Seleção da Barra Lateral Perdido Persistente
Layout de Login/Cadastro Verificações Condicionais Isolamento Natural via Grupos de Rotas
Busca de Dados do Usuário 12 buscas 1 layout compartilhado


3. Princípios dos Layouts Aninhados

(1) Comportamento de Persistência do Layout

100%
graph TB
    subgraph "Navegação de Página"
        A[layout raiz] --> B[layout dashboard]
        B --> C[página Dashboard]
        B --> D[página Lista de Projetos]
        B --> E[página Configurações]
    end

    subgraph "Comportamento Durante a Navegação"
        F[layout Mantém Montado<br/>Sem perda de estado]
        G[página Desmonta/Remonta<br/>Substituição de Conteúdo]
    end

    style F fill:#d4edda
    style G fill:#f8d7da
Comportamento layout page
Remonta durante a navegação ❌ Não remonta ✅ Remonta
Preservação do Estado React ✅ Preservado ❌ Reiniciado
useEffect reexecuta ❌ Não executa ✅ Executa
Busca Dados ❌ Não busca ✅ Busca

▶ Exemplo: Demonstração de Persistência do Layout

Saída:

TEXT 📖 Somente leitura
Diagrama: o layout persiste durante a navegação (mantém o estado), o template remonta (nova instância).
TSX
// ============================================
// Demonstração de Persistência: Layout vs Página Remontada
// ============================================

// src/app/(dashboard)/layout.tsx — Layout com Barra Lateral
'use client';

import { useState } from "react";
import Link from "next/link";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const [sidebarState, setSidebarState] = useState("collapsed");

  return (
    <div className="flex h-screen">
      {/* Barra Lateral — Mantém expandida/recolhida durante a navegação */}
      <aside className={`bg-gray-800 text-white ${sidebarState === "collapsed" ? "w-16" : "w-64"} transition-all`}>
        <button
          onClick={() => setSidebarState(s =>
            s === "collapsed" ? "expanded" : "collapsed"
          )}
          className="p-4 hover:bg-gray-700 w-full text-left"
        >
          {sidebarState === "collapsed" ? "→" : "← Recolher"}
        </button>
        <nav className="mt-4">
          <Link href="/dashboard" className="block p-3 hover:bg-gray-700">Dashboard</Link>
          <Link href="/dashboard/projects" className="block p-3 hover:bg-gray-700">Projetos</Link>
          <Link href="/dashboard/settings" className="block p-3 hover:bg-gray-700">Configurações</Link>
        </nav>
        <div className="mt-4 p-3 text-sm text-gray-400">
          O estado da barra lateral persiste durante a navegação
        </div>
      </aside>
      <main className="flex-1 p-8 overflow-auto">
        {children}
      </main>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
Componente interativo com estado: sidebarState.
TSX
// src/app/(dashboard)/dashboard/page.tsx
export default function DashboardPage() {
  return (
    <div>
      <h1 className="text-2xl font-bold">Dashboard</h1>
      <p className="text-gray-600">Bem-vindo ao seu painel de controle.</p>
    </div>
  );
}
TSX
// src/app/(dashboard)/dashboard/projects/page.tsx
export default function ProjectsPage() {
  return (
    <div>
      <h1 className="text-2xl font-bold">Projetos</h1>
      <p className="text-gray-600">Sua lista de projetos aparece aqui.</p>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
1. Acesse /dashboard, Expanda a barra lateral, Veja o Conteúdo do Dashboard
2. Clique em "← Recolher", A barra lateral recolhe para 64px
3. Clique no link "Projetos", Navegue para /dashboard/projects
4. A barra lateral permanece recolhida (Não foi redefinida para expandida) ✅
5. O conteúdo da página muda de Dashboard para Projetos ✅

(2) Hierarquia de Layouts Aninhados

100%
graph TB
    A[layout raiz] --> B[app/layout.tsx]
    B --> C[layout (dashboard)]
    C --> D[app/(dashboard)/layout.tsx]
    D --> E[layout products]
    E --> F[app/(dashboard)/products/layout.tsx]
    F --> G[página]
    G --> H[app/(dashboard)/products/page.tsx]

    style B fill:#cce5ff
    style D fill:#d4edda
    style F fill:#f8d7da
Nível de Layout Escopo Conteúdo Compartilhado
Layout Raiz Todas as Páginas <html>, <body>, Fontes Globais, Estilos Globais
Layout de Grupo Páginas dentro de um grupo Barra lateral, barra de navegação, informações do usuário
Layout Aninhado Páginas de Subdiretório Subnavegação, Breadcrumbs, Barra de Filtro Local

▶ Exemplo: Layout aninhado de três níveis

Saída:

TEXT 📖 Somente leitura
Diagrama da hierarquia de layout aninhado: raiz → pai → layouts filhos.
TSX
// ============================================
// Layout aninhado de três níveis: Global → Backend → Gerenciamento de Produtos
// ============================================

// Nível 1: src/app/layout.tsx — Layout Raiz
export default function RootLayout({ children }) {
  return (
    <html lang="pt-BR">
      <body className="bg-gray-50">
        {children}
      </body>
    </html>
  );
}

// Nível 2: src/app/(dashboard)/layout.tsx — Layout do Backend
export default function DashboardLayout({ children }) {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1">{children}</main>
    </div>
  );
}

// Nível 3: src/app/(dashboard)/products/layout.tsx — Portfólio de Produtos
export default function ProductsLayout({ children }) {
  return (
    <div>
      <nav className="flex gap-4 border-b pb-2 mb-4">
        <a href="/products" className="text-blue-600">Todos os Produtos</a>
        <a href="/products/add" className="text-blue-600">Adicionar Produto</a>
        <a href="/products/categories" className="text-blue-600">Categorias</a>
      </nav>
      {children}
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
RootLayout renderiza sua UI.

Saída:

TEXT 📖 Somente leitura
Acesse /dashboard/products:
→ Layout Raiz Renderizando <html><body>
→ Layout do Backend Renderizando <Sidebar> + <main>
→ Layout de Produtos Renderizando Sub-Navegação de Produtos + Conteúdo da Página
Todos os três níveis de aninhamento estão em efeito


4. Grupos de Rotas (group)

(1) O Que São Grupos de Rotas?

Diretórios (group) não geram segmentos de caminho nas URLs; são usados exclusivamente para agrupamento lógico.

100%
graph LR
    subgraph "Estrutura de Arquivos"
        A[app] --> B[(auth)]
        A --> C[(dashboard)]
        B --> D[login/page.tsx]
        B --> E[register/page.tsx]
        C --> F[page.tsx]
        C --> G[settings/page.tsx]
    end

    subgraph "URL Correspondente"
        H[/login]
        I[/register]
        J[/dashboard]
        K[/dashboard/settings]
    end

    style B fill:#f8d7da
    style C fill:#d4edda
Diretório Caminho da URL Descrição
app/(auth)/login/page.tsx /login (auth) Não gera segmentos de caminho
app/(auth)/register/page.tsx /register (auth) Não gera segmentos de caminho
app/(dashboard)/page.tsx /dashboard (dashboard) Não gera segmentos de caminho
app/(dashboard)/settings/page.tsx /dashboard/settings Subcaminho é normal

▶ Exemplo: Usando Grupos de Rotas para Implementar Layouts Diferentes

Saída:

TEXT 📖 Somente leitura
Diagrama da estrutura de rotas: caminhos do sistema de arquivos mapeiam para caminhos de URL.
TSX
// ============================================
// Usando Grupos de Rotas para Separar os layouts da página de login e do painel admin
// ============================================

// src/app/(auth)/layout.tsx — Layout de Login/Cadastro (Sem Navegação, Sem barra lateral)
export default function AuthLayout({ children }) {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
      <div className="w-full max-w-md">
        <div className="text-center mb-8">
          <h1 className="text-3xl font-bold text-gray-900">TaskFlow</h1>
          <p className="text-gray-500">Colabore e entregue</p>
        </div>
        <div className="bg-white p-8 rounded-xl shadow-sm">
          {children}
        </div>
      </div>
    </div>
  );
}

// src/app/(auth)/login/page.tsx
export default function LoginPage() {
  return (
    <form className="space-y-4">
      <h2 className="text-xl font-bold text-center">Entrar</h2>
      <input
        type="email"
        placeholder="Email"
        className="w-full p-3 border rounded-lg"
      />
      <input
        type="password"
        placeholder="Senha"
        className="w-full p-3 border rounded-lg"
      />
      <button
        type="submit"
        className="w-full p-3 bg-blue-600 text-white rounded-lg"
      >
        Entrar
      </button>
    </form>
  );
}

Saída:

TEXT 📖 Somente leitura
Renderiza: TaskFlow | Colabore e entregue
TSX
// src/app/(dashboard)/layout.tsx — Layout do Painel Admin (Barra Lateral + Navegação Superior)
export default function DashboardLayout({ children }) {
  return (
    <div className="flex h-screen">
      <aside className="w-64 bg-gray-900 text-white">
        <div className="p-4 text-xl font-bold">TaskFlow</div>
        <nav className="mt-4">
          <a href="/dashboard" className="block p-3 hover:bg-gray-800">
            Dashboard
          </a>
          <a href="/dashboard/projects" className="block p-3 hover:bg-gray-800">
            Projetos
          </a>
          <a href="/dashboard/settings" className="block p-3 hover:bg-gray-800">
            Configurações
          </a>
        </nav>
      </aside>
      <div className="flex-1 flex flex-col">
        <header className="bg-white shadow-sm p-4">
          <input
            type="search"
            placeholder="Pesquisar..."
            className="w-64 p-2 border rounded"
          />
        </header>
        <main className="flex-1 p-8 overflow-auto">{children}</main>
      </div>
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
Acesse /login e veja:
- Design de Formulário Centralizado (Sem barra de navegação, Sem barra lateral)
- Fundo com gradiente azul
- Logotipo da Marca TaskFlow + Formulário de Entrar

Acesse /dashboard e veja:
- Barra lateral escura à esquerda (Dashboard / Projetos / Configurações)
- Barra de pesquisa branca no topo
- Área de conteúdo à direita


5. Layout vs Modelo (Template)

(1) Principais Diferenças

100%
graph LR
    A[Navegar para uma nova página] --> B{layout ou template?}
    B -->|layout| C[Mantém Montado<br/>Estado Persistente]
    B -->|template| D[Desinstala e Reinstala<br/>Remonta]
    C --> E[Componente Filho Atualiza]
    D --> F[Componente filho + Todos os contêineres wrapper foram reconstruídos]

    style C fill:#d4edda
    style D fill:#f8d7da
Características layout.tsx template.tsx
Remonta durante a navegação ❌ Mantém montado ✅ Desmonta + Remonta
Preservação do Estado React ✅ Preservado ❌ Reiniciado
useEffect reexecuta ❌ Não executa ✅ Executa
Animações de Transição de Página Não adequado ✅ Adequado
Atualizar Dados ❌ Não atualiza ✅ Atualiza a cada navegação
Performance Melhor Ligeiramente pior (sobrecarga de reconstrução)

▶ Exemplo: Comparação de Comportamento entre Layout e Template

Saída:

TEXT 📖 Somente leitura
Diagrama: o layout persiste durante a navegação (mantém o estado), o template remonta (nova instância).
TSX
// ============================================
// Demonstração de Comparação de Comportamento: layout vs template
// ============================================

// src/app/(dashboard)/layout.tsx — Usando layout (Mantém o estado durante a navegação)
'use client';
import { useEffect, useState } from "react";
import Link from "next/link";

export default function DashboardLayout({ children }) {
  const [count, setCount] = useState(0);
  const [mountTime] = useState(new Date().toLocaleTimeString());

  useEffect(() => {
    console.log("Layout montado em:", new Date().toLocaleTimeString());
  }, []);

  return (
    <div className="border-2 border-blue-500 p-4 rounded m-4">
      <div className="text-sm text-blue-600 mb-2">
        [LAYOUT] Montado em: {mountTime} | Contador: {count}
        <button onClick={() => setCount(c => c + 1)} className="ml-2 px-2 bg-blue-100 rounded">
          +1
        </button>
      </div>
      <nav className="flex gap-4 mb-4">
        <Link href="/dashboard/page-a" className="text-blue-600">Página A</Link>
        <Link href="/dashboard/page-b" className="text-blue-600">Página B</Link>
      </nav>
      {children}
    </div>
  );
}

Saída:

TEXT 📖 Somente leitura
Componente interativo com estado: count.
TSX
// ============================================
// template.tsx — Reconstruído Durante a Navegação
// Arquivo: src/app/(dashboard)/template.tsx
// ============================================

'use client';
import { useEffect, useState } from "react";
import Link from "next/link";

export default function DashboardTemplate({ children }) {
  const [count, setCount] = useState(0);
  const [mountTime] = useState(new Date().toLocaleTimeString());

  useEffect(() => {
    console.log("Template montado em:", new Date().toLocaleTimeString());
  }, []);

  return (
    <div className="border-2 border-red-500 p-4 rounded m-4">
      <div className="text-sm text-red-600 mb-2">
        [TEMPLATE] Montado em: {mountTime} | Contador: {count}
        <button onClick={() => setCount(c => c + 1)} className="ml-2 px-2 bg-red-100 rounded">
          +1
        </button>
      </div>
      <nav className="flex gap-4 mb-4">
        <Link href="/dashboard/page-a" className="text-red-600">Página A</Link>
        <Link href="/dashboard/page-b" className="text-red-600">Página B</Link>
      </nav>
      {children}
    </div>
  );
}
TSX
// src/app/(dashboard)/page-a.tsx
export default function PageA() {
  return <div className="text-lg">Conteúdo da Página A</div>;
}

// src/app/(dashboard)/page-b.tsx
export default function PageB() {
  return <div className="text-lg">Conteúdo da Página B</div>;
}

Saída:

TEXT 📖 Somente leitura
1. Acesse /dashboard/page-a:
   [LAYOUT] Montado em: 10:30:00 | Contador: 0
   [TEMPLATE] Montado em: 10:30:00 | Contador: 0
   Conteúdo da Página A

2. Clique no botão Contador +1 (os contadores dos dois contêineres incrementam independentemente)

3. Clique no link "Página B":
   [LAYOUT] Montado em: 10:30:00 | Contador: 1  ← layout Mantido, Estado Preservado
   [TEMPLATE] Montado em: 10:30:05 | Contador: 0  ← template Reconstruído, Estado Reiniciado
   Conteúdo da Página B

Conclusão: layout Mantém Montagem e Estado, template é reconstruído a cada navegação

(2) Quando Usar um Template

Cenário Recomendação Motivo
Animações de Transição de Página template Animações de Entrada/Saída do framer-motion
Dados devem ser atualizados a cada navegação template useEffect reexecuta
Rastreamento de Análise de Navegação template Aciona o código de rastreamento a cada navegação
Barra de Navegação/Barra Lateral layout Mantém selecionado e expandido/recolhido
Carrinho de Compras/Player layout Estado de UI Persistente

▶ Exemplo: Implementando animações de transição de página usando templates

Saída:

TEXT 📖 Somente leitura
O componente renderiza a UI descrita no navegador.
TSX
// ============================================
// template + framer-motion Implementando Animações de Transição de Página
// ============================================

'use client';
import { motion } from "framer-motion";

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -20 }}
      transition={{ duration: 0.3 }}
    >
      {children}
    </motion.div>
  );
}

Saída:

TEXT 📖 Somente leitura
Renderiza a UI do componente Template.

Saída:

TEXT 📖 Somente leitura
Toda vez que você navega para uma página:
1. A página antiga desaparece para cima (opacity: 1→0, y: 0→-20)
2. A nova página surge de baixo (opacity: 0→1, y: 20→0)
3. Duração da Animação: 300ms
4. layout Mantido, Não Reconstruído, Animações se aplicam apenas à área de conteúdo


6. Configuração Essencial para Layouts Raiz

(1) Responsabilidades do Layout Raiz

O layout raiz é o único arquivo de layout obrigatório; ele define a estrutura para todas as páginas:

Opção de Configuração Código Descrição
Tag <html> <html lang="pt-BR"> Atributos de idioma que afetam o SEO
Tag <body> <body className="..."> Classe CSS Global
Carregamento de Fontes next/font/google Fontes Otimizadas para Performance
Metadados export const metadata Metadados Globais de SEO
Estilos Globais import './globals.css' Diretivas do Tailwind

▶ Exemplo: Layout Raiz Completo

Saída:

TEXT 📖 Somente leitura
A página renderiza conforme descrito acima, com a UI atualizando com base no comportamento descrito.
TSX
// ============================================
// Layout Raiz — Configuração Completa
// Arquivo: src/app/layout.tsx
// ============================================

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

// Otimização de Fonte Google (Pré-carregamento automático + CSS size-adjust)
const inter = Inter({
  subsets: ["latin"],
  display: "swap",
  variable: "--font-inter",
});

// Metadados globais de SEO
export const metadata: Metadata = {
  title: {
    template: "%s | TaskFlow",
    default: "TaskFlow - Gerenciamento de Projetos",
  },
  description: "Uma plataforma colaborativa de gerenciamento de projetos",
  openGraph: {
    title: "TaskFlow",
    description: "Colabore e entregue projetos mais rapidamente",
  },
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="pt-BR" className={inter.variable}>
      <body className="antialiased bg-gray-50 text-gray-900 min-h-screen">
        {children}
      </body>
    </html>
  );
}

Saída:

TEXT 📖 Somente leitura
Renderiza: Layout raiz com fonte Inter, classes CSS globais e template de metadados SEO ("%s | TaskFlow").

Saída:

TEXT 📖 Somente leitura
Todas as páginas são geradas automaticamente com:
- Fonte Inter (Otimização de Performance, zero CLS)
- Classes CSS globais (antialiased, bg-gray-50, text-gray-900)
- Metadados SEO (template de título "Página | TaskFlow")
- Tags Open Graph (Cartões de Compartilhamento em Redes Sociais)


7. Modelos de Compartilhamento de Dados

(1) Três Modelos de Compartilhamento

100%
graph TB
    subgraph "Modelos de Compartilhamento de Dados"
        A[1. Passagem de Props<br/>layout → página]
        B[2. Context Provider<br/>Estado Global]
        C[3. Busca de Dados Paralela<br/>layout + página Obtêm individualmente]
    end

    style A fill:#d4edda
    style B fill:#cce5ff
    style C fill:#f8d7da
Modo Cenários Aplicáveis Vantagens Desvantagens
Passagem de Props layout busca dados e passa para a página type-safe passa apenas um nível abaixo
Context Provider Informações do usuário, temas Disponível globalmente Necessário para Client Component
Busca de Dados Paralela Dados independentes para layout e página Desacoplamento, concorrência Não pode ser compartilhado diretamente


8. Exemplo Completo: Sistema de Layout Completo do TaskFlow

TSX
// ============================================
// Exemplo Abrangente: Sistema de Layout Completo do TaskFlow
// Layout Raiz + Layout de Autenticação + Layout do Dashboard + Portfólio de Produtos
// ============================================

// src/app/layout.tsx — Layout Raiz
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"], display: "swap" });

export const metadata: Metadata = {
  title: { template: "%s | TaskFlow", default: "TaskFlow" },
  description: "Plataforma de gerenciamento de projetos",
};

export default function RootLayout({ children }) {
  return (
    <html lang="pt-BR" className={inter.className}>
      <body className="bg-gray-50 antialiased">{children}</body>
    </html>
  );
}

// src/app/(auth)/layout.tsx — Layout de Autenticação
export default function AuthLayout({ children }) {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-600 to-indigo-700">
      <div className="w-full max-w-md">
        <div className="text-center text-white mb-8">
          <h1 className="text-4xl font-bold">TaskFlow</h1>
          <p className="text-blue-200 mt-2">Colabore e entregue</p>
        </div>
        <div className="bg-white rounded-xl shadow-2xl p-8">
          {children}
        </div>
      </div>
    </div>
  );
}

// src/app/(dashboard)/layout.tsx — Layout do Dashboard
'use client';
import { createContext, useContext, useState } from "react";
import Link from "next/link";

const DashboardContext = createContext(null);
export function useDashboard() { return useContext(DashboardContext); }

export default function DashboardLayout({ children }) {
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const user = { name: "Alice", role: "Admin", avatar: "/avatar.png" };

  return (
    <DashboardContext.Provider value={{ user, sidebarOpen, setSidebarOpen }}>
      <div className="flex h-screen">
        <aside className={`bg-gray-900 text-white ${sidebarOpen ? "w-64" : "w-16"} transition-all duration-300`}>
          <div className="p-4 flex items-center gap-3">
            <div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-sm">
              {user.name[0]}
            </div>
            {sidebarOpen && <span className="font-bold">TaskFlow</span>}
          </div>
          <nav className="mt-4">
            <Link href="/dashboard" className="flex items-center gap-3 p-3 hover:bg-gray-800">
              <span>📊</span>
              {sidebarOpen && <span>Dashboard</span>}
            </Link>
            <Link href="/dashboard/projects" className="flex items-center gap-3 p-3 hover:bg-gray-800">
              <span>📁</span>
              {sidebarOpen && <span>Projetos</span>}
            </Link>
            <Link href="/dashboard/team" className="flex items-center gap-3 p-3 hover:bg-gray-800">
              <span>👥</span>
              {sidebarOpen && <span>Equipe</span>}
            </Link>
            <Link href="/dashboard/settings" className="flex items-center gap-3 p-3 hover:bg-gray-800">
              <span>⚙️</span>
              {sidebarOpen && <span>Configurações</span>}
            </Link>
          </nav>
          <button
            onClick={() => setSidebarOpen(!sidebarOpen)}
            className="absolute bottom-4 left-4 text-gray-400 hover:text-white"
          >
            {sidebarOpen ? "◀" : "▶"}
          </button>
        </aside>
        <div className="flex-1 flex flex-col">
          <header className="bg-white shadow-sm px-8 py-4 flex items-center justify-between">
            <h2 className="text-lg font-semibold text-gray-700">
              Bem-vinda, {user.name}
            </h2>
            <div className="flex items-center gap-4">
              <button className="text-gray-500">🔔</button>
              <div className="w-8 h-8 bg-gray-300 rounded-full" />
            </div>
          </header>
          <main className="flex-1 p-8 overflow-auto">{children}</main>
        </div>
      </div>
    </DashboardContext.Provider>
  );
}

// src/app/(dashboard)/dashboard/page.tsx — Página Inicial do Dashboard
'use client';
import { useDashboard } from "../layout";

export default function DashboardHomePage() {
  const { user } = useDashboard();

  return (
    <div>
      <h1 className="text-3xl font-bold">Visão Geral do Dashboard</h1>
      <p className="text-gray-500 mt-2">
        Bem-vinda de volta, {user.name}! Aqui está o resumo dos seus projetos.
      </p>
      <div className="grid grid-cols-3 gap-6 mt-8">
        <div className="bg-white p-6 rounded-xl shadow-sm">
          <div className="text-sm text-gray-500">Projetos Ativos</div>
          <div className="text-3xl font-bold mt-2">12</div>
        </div>
        <div className="bg-white p-6 rounded-xl shadow-sm">
          <div className="text-sm text-gray-500">Tarefas Pendentes</div>
          <div className="text-3xl font-bold mt-2">48</div>
        </div>
        <div className="bg-white p-6 rounded-xl shadow-sm">
          <div className="text-sm text-gray-500">Membros da Equipe</div>
          <div className="text-3xl font-bold mt-2">8</div>
        </div>
      </div>
    </div>
  );
}

Saída Esperada:

TEXT 📖 Somente leitura
Acesse /login:
- Fundo em tela cheia com gradiente azul escuro
- Cartão de Formulário Branco Centralizado
- Logotipo TaskFlow + Formulário de Login

Acesse /dashboard:
- Barra lateral escura à esquerda (Dobrável, Mantém Estado Durante a Navegação)
- Barra de Navegação Superior (Mostra Bem-vinda, Alice + Ícone de Notificação)
- Área de conteúdo à direita (Visão Geral do Dashboard + 3 Cartões de Estatísticas)
- Os dados do usuário são transmitidos via Context Compartilhado

❓ Perguntas Frequentes

P: Posso usar 'use client' no layout.tsx? R: Sim. layout.tsx é um Server Component por padrão, mas se você precisar usar useState, useEffect ou manipuladores de eventos, pode adicionar a diretiva 'use client'. Nota: Uma vez adicionada, este layout e todos os seus componentes filhos se tornam Client Components.

P: Como os Grupos de Rotas afetam as URLs? R: De forma alguma. A URL para (auth)/login/page.tsx permanece /login; (auth) afeta apenas a estrutura do layout e não gera segmentos de caminho na URL. Este é o propósito central dos Grupos de Rotas.

P: Layout e template podem coexistir? R: Sim. Se ambos layout.tsx e template.tsx estiverem presentes no mesmo diretório, o template será envolvido dentro do layout. Quando o usuário navega, o layout permanece e o template é reconstruído.

P: A tag <html> no layout raiz pode ser modificada? R: Sim, e você deve escrevê-la manualmente. O layout raiz substitui automaticamente o wrapper HTML padrão do Next.js, então você está livre para adicionar atributos como lang, dir, className e assim por diante. Isso é essencial para o suporte a RTL.

P: Qual é o número máximo de níveis de layout aninhado? R: Não há um limite estrito, mas recomendamos não exceder 3–4 níveis. Cada nível de layout cria uma div wrapper adicional no DOM, e muitos níveis aninhados podem impactar a performance e a manutenibilidade.

P: Como os dados podem ser compartilhados entre diferentes Grupos de Rotas? R: Através do Context Provider no layout raiz. Ao envolver UserProvider, ThemeProvider e assim por diante no layout raiz, as páginas dentro de todos os Grupos de Rotas podem acessá-los. Este é o método de compartilhamento global de dados recomendado pelo Next.js.


📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie dois layouts de Grupo de Rotas, (marketing)/layout.tsx e (app)/layout.tsx, no projeto, e aplique estilos diferentes às páginas de marketing (Início, Sobre) e às páginas do aplicativo (Dashboard, Configurações), respectivamente.

  2. Exercício Avançado (⭐⭐): Coloque tanto layout.tsx quanto template.tsx no mesmo diretório. Adicione useEffect(() => { console.log('montado') }, []) em cada arquivo, depois navegue pelas páginas e observe a saída do console para verificar as diferenças na forma como são montados.

  3. Desafio (⭐⭐⭐): Crie um UserProvider (Context) no layout raiz e use useUser() tanto em (auth)/login/page.tsx quanto em (dashboard)/page.tsx para ler os dados do usuário, verificando se o Context compartilha dados entre Grupos de Rotas.

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%