Next.js App Router - Guia Completo
O App Router é a nova arquitetura de roteamento do Next.js 13+, baseada em React Server Components e oferecendo melhor performance e experiência de desenvolvimento.
Estrutura de Pastas
No App Router, a estrutura de pastas define automaticamente as rotas da aplicação.
app/
├── layout.tsx # Layout raiz
├── page.tsx # Página inicial (/)
├── loading.tsx # UI de loading
├── error.tsx # UI de erro
├── not-found.tsx # Página 404
├── global-error.tsx # Erro global
│
├── about/
│ └── page.tsx # /about
│
├── blog/
│ ├── page.tsx # /blog
│ ├── loading.tsx # Loading específico do blog
│ └── [slug]/
│ └── page.tsx # /blog/[slug]
│
└── dashboard/
├── layout.tsx # Layout aninhado
├── page.tsx # /dashboard
├── settings/
│ └── page.tsx # /dashboard/settings
└── (overview)/ # Grupo de rotas
└── page.tsx # /dashboard (mesmo que acima)
Server vs Client Components
Por padrão, todos os componentes no App Router são Server Components, executados no servidor para melhor performance.
// Server Component (padrão)
// Executa no servidor, pode acessar banco de dados diretamente
async function BlogPost({ params }: { params: { slug: string } }) {
// Fetch de dados no servidor
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
.then(res => res.json());
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
<p>Publicado em: {new Date(post.date).toLocaleDateString()}</p>
</article>
);
}
export default BlogPost;
'use client' // Diretiva para Client Component
import { useState } from 'react';
// Client Component
// Executa no navegador, pode usar hooks e interatividade
function InteractiveCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Contador: {count}</p>
<button onClick={() => setCount(count + 1)}>
Incrementar
</button>
</div>
);
}
export default InteractiveCounter;
Layouts Aninhados
Os layouts no App Router são aninhados automaticamente, permitindo interfaces consistentes em diferentes níveis da aplicação.
// app/layout.tsx - Layout raiz
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="pt-BR">
<body>
<header>
<nav>Navegação Global</nav>
</header>
<main>{children}</main>
<footer>Rodapé Global</footer>
</body>
</html>
);
}
// app/dashboard/layout.tsx - Layout do dashboard
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="dashboard-container">
<aside>
<nav>
<a href="/dashboard">Overview</a>
<a href="/dashboard/settings">Configurações</a>
<a href="/dashboard/profile">Perfil</a>
</nav>
</aside>
<div className="dashboard-content">
{children}
</div>
</div>
);
}
Loading e Error States
O App Router oferece arquivos especiais para gerenciar estados de loading e erro de forma declarativa.
// app/blog/loading.tsx
export default function Loading() {
return (
<div className="loading-container">
<div className="spinner" />
<p>Carregando posts...</p>
</div>
);
}
// app/blog/error.tsx
'use client' // Error components devem ser Client Components
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="error-container">
<h2>Algo deu errado!</h2>
<p>{error.message}</p>
<button onClick={reset}>
Tentar novamente
</button>
</div>
);
}
Data Fetching
No App Router, o data fetching é simplificado com async/await diretamente nos Server Components.
// Fetch simples
async function Posts() {
const posts = await fetch('https://api.example.com/posts', {
// Revalidação a cada 60 segundos
next: { revalidate: 60 }
}).then(res => res.json());
return (
<div>
{posts.map((post: any) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
// Fetch com cache personalizado
async function UserProfile({ userId }: { userId: string }) {
const user = await fetch(`https://api.example.com/users/${userId}`, {
// Cache até ser invalidado manualmente
cache: 'force-cache'
}).then(res => res.json());
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// Fetch sem cache (sempre fresh)
async function RealTimeData() {
const data = await fetch('https://api.example.com/live-data', {
cache: 'no-store'
}).then(res => res.json());
return <div>Dados em tempo real: {data.value}</div>;
}
Route Handlers (API Routes)
Os Route Handlers substituem as API Routes do Pages Router, oferecendo mais flexibilidade e melhor integração.
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
// GET /api/posts
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const page = searchParams.get('page') || '1';
const posts = await fetchPosts(parseInt(page));
return NextResponse.json(posts);
}
// POST /api/posts
export async function POST(request: NextRequest) {
const body = await request.json();
const newPost = await createPost(body);
return NextResponse.json(newPost, { status: 201 });
}
// app/api/posts/[id]/route.ts
// GET /api/posts/123
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const post = await fetchPost(params.id);
if (!post) {
return NextResponse.json(
{ error: 'Post não encontrado' },
{ status: 404 }
);
}
return NextResponse.json(post);
}
Middleware
O middleware no App Router permite interceptar requests antes que cheguem às rotas, ideal para autenticação e redirecionamentos.
// middleware.ts (na raiz do projeto)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Verificar autenticação
const token = request.cookies.get('auth-token');
// Proteger rotas do dashboard
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// Redirecionamento baseado em geolocalização
const country = request.geo?.country || 'US';
if (request.nextUrl.pathname === '/') {
if (country === 'BR') {
return NextResponse.redirect(new URL('/pt-br', request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: [
// Aplicar middleware a todas as rotas exceto arquivos estáticos
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
Streaming e Suspense
O App Router suporta streaming nativo, permitindo carregar partes da página progressivamente.
import { Suspense } from 'react';
// Componente que demora para carregar
async function SlowComponent() {
// Simula delay
await new Promise(resolve => setTimeout(resolve, 3000));
const data = await fetch('https://api.example.com/slow-data')
.then(res => res.json());
return <div>Dados carregados: {data.result}</div>;
}
// Página com streaming
export default function StreamingPage() {
return (
<div>
<h1>Página com Streaming</h1>
<p>Este conteúdo carrega imediatamente</p>
<Suspense fallback={<div>Carregando dados...</div>}>
<SlowComponent />
</Suspense>
<p>Este conteúdo também carrega imediatamente</p>
</div>
);
}
- Performance: Server Components reduzem bundle JavaScript
- Streaming: Carregamento progressivo da interface
- Layouts: Sistema de layouts mais flexível
- Data Fetching: Mais simples e poderoso
- Caching: Sistema de cache mais granular
Migração do Pages Router
A migração pode ser feita gradualmente, mantendo ambos os sistemas funcionando simultaneamente.
projeto/
├── app/ # Novas rotas (App Router)
│ ├── dashboard/
│ └── blog/
├── pages/ # Rotas existentes (Pages Router)
│ ├── api/
│ ├── about.tsx
│ └── contact.tsx
└── ...
Conclusão
O App Router representa o futuro do Next.js, oferecendo melhor performance, experiência de desenvolvimento mais intuitiva e recursos avançados como Server Components e streaming. A migração pode ser gradual, permitindo adoção progressiva dos novos recursos.