App Router e Layouts
Aula 1 de 6
App Router
O Next.js App Router (app/ directory) é o sistema de roteamento moderno baseado em file system, React Server Components e layouts aninhados.
app/
├── layout.tsx # Layout raiz (obrigatório)
├── page.tsx # Rota "/"
├── loading.tsx # Loading state (Suspense)
├── error.tsx # Error boundary
├── not-found.tsx # 404
├── about/
│ ├── page.tsx # Rota "/about"
│ └── layout.tsx # Layout específico do about
├── blog/
│ ├── page.tsx # Rota "/blog"
│ └── [slug]/
│ └── page.tsx # Rota "/blog/meu-post"
└── dashboard/
├── page.tsx # Rota "/dashboard"
└── settings/
└── page.tsx # Rota "/dashboard/settings"
page.tsx
// app/page.tsx
export default function HomePage() {
return (
<div>
<h1>Home</h1>
</div>
);
}
layout.tsx
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Meu App',
description: 'Descrição do app'
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="pt-BR">
<body>
<header>
<nav>{/* Navbar global */}</nav>
</header>
<main>{children}</main>
<footer>{/* Footer global */}</footer>
</body>
</html>
);
}
loading.tsx
// app/loading.tsx
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500" />
</div>
);
}
error.tsx
// app/error.tsx
'use client';
export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2>Algo deu errado!</h2>
<p>{error.message}</p>
<button onClick={reset}>Tentar novamente</button>
</div>
);
}
not-found.tsx
// app/not-found.tsx
export default function NotFound() {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h1 className="text-6xl font-bold">404</h1>
<p>Página não encontrada</p>
<a href="/">Voltar ao início</a>
</div>
);
}
Layouts Aninhados
Os layouts persistem entre navegações, mantendo estado.
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section className="flex">
<aside className="w-64 bg-gray-100 p-4">
<nav>
<a href="/dashboard">Overview</a>
<a href="/dashboard/settings">Configurações</a>
</nav>
</aside>
<div className="flex-1 p-4">{children}</div>
</section>
);
}
Route Groups
Agrupe rotas sem afetar a URL.
app/
├── (marketing)/
│ ├── layout.tsx # Layout para landing pages
│ ├── page.tsx # "/"
│ └── pricing/
│ └── page.tsx # "/pricing"
└── (dashboard)/
├── layout.tsx # Layout para dashboard
├── dashboard/
│ └── page.tsx # "/dashboard"
└── settings/
└── page.tsx # "/settings"
// app/(dashboard)/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gray-50">
{children}
</div>
);
}
Metadata API
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
type Props = {
params: { slug: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage]
},
twitter: {
card: 'summary_large_image',
title: post.title
}
};
}
export default async function PostPage({ params }: Props) {
const post = await getPost(params.slug);
return <article>{/* ... */}</article>;
}
Lab: Exercício - Layout Completo
// app/layout.tsx
import type { Metadata } from 'next';
import './globals.css';
export const metadata: Metadata = {
title: {
template: '%s | Meu App',
default: 'Meu App'
},
description: 'Aplicação Next.js'
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="pt-BR">
<body>
<nav className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4">
<a href="/" className="font-bold text-xl">Logo</a>
</div>
</nav>
{children}
</body>
</html>
);
}
// app/(dashboard)/layout.tsx
import Link from 'next/link';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex min-h-screen">
<aside className="w-64 bg-gray-900 text-white p-6">
<nav className="space-y-4">
<Link href="/dashboard">Dashboard</Link>
<Link href="/dashboard/users">Usuários</Link>
<Link href="/dashboard/settings">Configurações</Link>
</nav>
</aside>
<main className="flex-1 p-8">{children}</main>
</div>
);
}
# Criar estrutura
npx create-next-app@latest my-app --app --typescript
App Router usa Server Components por padrão. Layouts mantêm estado entre navegações. Route Groups organizam rotas sem alterar URLs. Metadata API gera tags SEO dinâmicas e estáticas.