Layouts
Layouts envolvem páginas com UI compartilhada que persiste entre navegações. Eles nunca são remontados - use templates quando precisar de estado fresco.
Busque em todas as páginas da documentação
Layouts envolvem páginas com UI compartilhada que persiste entre navegações. Eles nunca são remontados - use templates quando precisar de estado fresco.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
// app/layout.tsx - Layout raiz (obrigatório, exatamente um)
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<header>Cabeçalho do Site</header>
<main>{children}</main>
<footer>Rodapé do Site</footer>
</body>
</html>
);
}
// app/dashboard/layout.tsx - Layout aninhado para /dashboard/*
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<nav className="w-64">Barra Lateral</nav>
<section className="flex-1">{children}</section>
</div>
);
}Quando usar isso: Sempre que várias páginas compartilham a mesma UI de wrapper - barras de navegação, barras laterais, provedores ou contêineres estruturais.
// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: { default: "Meu App", template: "%s | Meu App" },
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>
<nav className="border-b px-6 py-3">
<a href="/">Home</a>
<a href="/dashboard" className="ml-4">Dashboard</a>
</nav>
{children}
</body>
</html>
);
}// app/dashboard/layout.tsx
import Link from "next/link";
const sidebarLinks = [
{ href: "/dashboard", label: "Visão Geral" },
{ href: "/dashboard/analytics", label: "Analytics" },
{ href: "/dashboard/settings", label: "Configurações" },
];
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen">
<aside className="w-64 border-r p-4">
<h2 className="mb-4 font-bold">Dashboard</h2>
<ul className="space-y-2">
{sidebarLinks.map((link) => (
<li key={link.href}>
<Link href={link.href} className="text-blue-600 hover:underline">
{link.label}
</Link>
</li>
))}
</ul>
</aside>
<main className="flex-1 p-6">{children}</main>
</div>
);
}// app/dashboard/page.tsx
export default function DashboardPage() {
return <h1>Visão Geral do Dashboard</h1>;
}
// app/dashboard/analytics/page.tsx
export default function AnalyticsPage() {
return <h1>Analytics</h1>;
// Navegar para cá mantém a barra lateral montada - sem remontagem, sem perda de estado
}<html> e <body>. O Next.js gerará um erro se este arquivo estiver faltando.children. A prop children é a página atual ou o próximo layout aninhado na árvore.app/layout.tsx envolve app/dashboard/layout.tsx que envolve app/dashboard/page.tsx. Você nunca os compõe manualmente."use client" apenas quando o próprio layout precisar de hooks ou APIs do navegador.metadata ou uma função generateMetadata que se funde com os metadados do pai.// Template - é remontado a cada navegação (útil para animações de entrada/saída)
// app/dashboard/template.tsx
"use client";
import { motion } from "framer-motion";
export default function DashboardTemplate({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
);
}// Layout com params - acessando parâmetros de rota em um layout
// app/blog/[slug]/layout.tsx
export default async function BlogPostLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return (
<article>
<div className="text-sm text-gray-500">Post: {slug}</div>
{children}
</article>
);
}// Layout condicional - UI diferente com base no status de autenticação
// app/dashboard/layout.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth();
if (!session) redirect("/login");
return (
<div>
<p>Bem-vindo, {session.user.name}</p>
{children}
</div>
);
}// Tipo das props do Layout
interface LayoutProps {
children: React.ReactNode;
params: Promise<Record<string, string>>; // assíncrono no Next.js 15+
}
// Template tem as mesmas props do layout
interface TemplateProps {
children: React.ReactNode;
}
// Tipos de Metadados
import type { Metadata, ResolvingMetadata } from "next";
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> },
parent: ResolvingMetadata
): Promise<Metadata> {
const { slug } = await params;
return { title: slug };
}children. Use Contexto React, cookies ou um fetch de dados compartilhado para compartilhar dados entre um layout e suas páginas filhas.<html> e <body>. Mesmo como um Client Component, ele deve renderizar essas tags.layout.tsx > template.tsx > page.tsx. O template fica no meio.searchParams. Apenas page.tsx recebe searchParams. Se um layout precisar de parâmetros de consulta, use useSearchParams() em um filho Client Component.app/dashboard/layout.tsx, todas as páginas do dashboard perderão esse wrapper instantaneamente.params são assíncronos no Next.js 15+. Sempre use await params em layouts - o padrão de desestruturação síncrona é obsoleto.| Abordagem | Quando Usar |
|---|---|
template.tsx | Precisa de remontagem do componente a cada navegação (animações, logging) |
| Grupos de Rotas com layouts separados | Layouts diferentes para seções diferentes na mesma profundidade de URL |
| Provedor de contexto no lado do cliente | Compartilhando estado entre páginas sem um wrapper visual |
Rotas Paralelas (@slot) | Renderizando várias páginas lado a lado em um layout |
De uma aplicação SaaS Next.js 15 / React 19 em produção (SystemsArchitect.io).
// Exemplo de produção: Layout raiz com provedores e metadados
// Arquivo: app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { ThemeProvider } from '@/components/theme-provider';
import { ToastProvider } from '@/components/toast-provider';
import { ProjectStoreInitializer } from '@/components/project-store-initializer';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: { default: 'SystemsArchitect', template: '%s | SystemsArchitect' },
description: 'Plataforma de aprendizado de arquitetura de nuvem',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<ToastProvider>
<ProjectStoreInitializer>
{children}
</ProjectStoreInitializer>
</ToastProvider>
</ThemeProvider>
</body>
</html>
);
}// Exemplo de produção: Página inicial com dados do servidor
// Arquivo: app/page.tsx
import { getFeaturedContent } from '@/lib/content';
export default async function HomePage() {
const featured = await getFeaturedContent();
return (
<main>
<h1>Bem-vindo ao SystemsArchitect</h1>
{featured.map((item) => (
<article key={item.id}>{item.title}</article>
))}
</main>
);
}O que isso demonstra em produção:
ThemeProvider envolve tudo porque as notificações de toast e o inicializador de loja precisam de contexto de tema. ToastProvider vem antes de ProjectStoreInitializer porque os erros de inicialização da loja precisam exibir mensagens de toast.suppressHydrationWarning na tag <html> é necessário ao usar provedores de tema que definem um atributo class ou data-theme no servidor. Sem ele, a incompatibilidade entre a renderização do servidor e a renderização do cliente aciona um aviso do React.ThemeProvider, ToastProvider e ProjectStoreInitializer são inicializados uma vez e persistem em todas as transições de página. Qualquer estado nesses provedores é preservado.metadata.title.template ('%s | SystemsArchitect') permite que as páginas filhas definam apenas seu título (por exemplo, export const metadata = { title: 'Dashboard' }) e ele renderiza automaticamente como "Dashboard | SystemsArchitect".getServerSideProps do Pages Router.O app/layout.tsx raiz deve existir e conter as tags <html> e <body>. O Next.js gera um erro se este arquivo estiver faltando porque ele define a estrutura do documento HTML para todo o aplicativo.
Não. Layouts persistem entre navegações. O React os reconcilia para que o estado do componente, efeitos e DOM sejam preservados. É por isso que barras laterais e barras de navegação permanecem interativas.
layout.tsx persiste e não é remontado na navegaçãotemplate.tsx é remontado a cada navegação, fornecendo estado frescolayout.tsx > template.tsx > page.tsxNão. Layouts recebem apenas children como prop. Use Contexto React, cookies ou um fetch de dados compartilhado para compartilhar dados entre um layout e suas páginas filhas.
Não. Apenas page.tsx recebe searchParams. Se um layout precisar de parâmetros de consulta, use useSearchParams() em um filho Client Component.
// app/blog/[slug]/layout.tsx
export default async function BlogLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return (
<article>
<p>Post: {slug}</p>
{children}
</article>
);
}Defina title: { default: "Meu App", template: "%s | Meu App" } no layout raiz. As páginas filhas exportam metadata = { title: "Dashboard" } e ele renderiza como "Dashboard | Meu App".
Quebra. params agora é uma Promise no Next.js 15+. Você deve usar await params em layouts. O padrão síncrono é obsoleto.
interface LayoutProps {
children: React.ReactNode;
params: Promise<Record<string, string>>;
}
interface TemplateProps {
children: React.ReactNode;
}import type { Metadata, ResolvingMetadata } from "next";
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> },
parent: ResolvingMetadata
): Promise<Metadata> {
const { slug } = await params;
return { title: slug };
}Sim, mas ele ainda deve renderizar as tags <html> e <body>. Mesmo como um Client Component, essas tags são necessárias.
app/layout.tsx envolve app/dashboard/layout.tsx que envolve app/dashboard/page.tsx. Você nunca os compõe manualmente. O Next.js lida com o aninhamento com base na hierarquia do sistema de arquivos.
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥