kb.erickguedes.com
Next.js: React Full-Stack

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.