Next.js 15 solidifica o futuro full-stack do React. Com o App Router estável, React Server Components como padrão e Turbopack substituindo Webpack, construir aplicativos Next.js de produção em 2026 é mais rápido e poderoso do que nunca.
📋 Table of Contents
- O que há de novo no Next.js 15
- Configuração do projeto
- Fundamentos do roteador de aplicativos
- Componentes de servidor versus componentes de cliente
- Busca de dados com componentes de servidor
- Ações do servidor
- Manipuladores de rota (rotas de API)
- Pré-renderização parcial (PPR)
- Middleware
- Otimização de imagem
- Implantação e Produção
O que há de novo no Next.js 15
- Turbopack estável— Construções 10x mais rápidas, HMR 700x mais rápido vs Webpack
- Suporte de primeira classe para React 19— Componentes do servidor, ações, gancho use()
- Pré-renderização parcial (PPR)— shell estático + ilhas dinâmicas na mesma página
- Cache aprimorado– não há mais comportamento surpreendente de cache por padrão
- APIs dinâmicas— cookies(), headers(), searchParams agora são assíncronos
Configuração do projeto
# Create new Next.js 15 project
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack
cd my-app
# Project structure
# app/
# layout.tsx — root layout
# page.tsx — home page
# globals.css
# (dashboard)/ — route group (no URL segment)
# page.tsx
# blog/
# [slug]/
# page.tsx — dynamic route
# api/
# users/
# route.ts — API route handler
Fundamentos do roteador de aplicativos
// app/layout.tsx — Root Layout (required)
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: { template: "%s | My App", default: "My App" },
description: "Built with Next.js 15",
openGraph: {
type: "website",
locale: "en_US",
url: "https://myapp.com",
siteName: "My App",
},
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
Componentes de servidor versus componentes de cliente
// Server Component (default) — runs on server, no JavaScript sent to client
// app/blog/page.tsx
async function BlogPage() {
// Direct database/API access — no useEffect needed
const posts = await db.query("SELECT * FROM posts ORDER BY created_at DESC");
return (
<main>
<h1>Blog</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
// Client Component — add "use client" directive
"use client";
import { useState } from "react";
function SearchBar({ onSearch }: { onSearch: (q: string) => void }) {
const [query, setQuery] = useState("");
return (
<input
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={e => e.key === "Enter" && onSearch(query)}
placeholder="Search..."
/>
);
}
Busca de dados com componentes de servidor
// Parallel data fetching — no waterfalls
async function Dashboard() {
// These run in parallel
const [user, posts, stats] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchStats(),
]);
return (
<div>
<UserCard user={user} />
<PostList posts={posts} />
<StatsPanel stats={stats} />
</div>
);
}
// Dynamic rendering with searchParams (Next.js 15: async)
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ q?: string; page?: string }>;
}) {
const { q = "", page = "1" } = await searchParams;
const results = await searchPosts(q, parseInt(page));
return <SearchResults results={results} query={q} />;
}
Ações do servidor
As ações do servidor permitem executar o código do servidor a partir de formulários e manipuladores de eventos:
// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export async function createPost(formData: FormData) {
const title = formData.get("title") as string;
const content = formData.get("content") as string;
// Validate
if (!title || title.length < 3) {
return { error: "Title must be at least 3 characters" };
}
// Save to database
await db.posts.create({ title, content });
// Revalidate and redirect
revalidatePath("/blog");
redirect("/blog");
}
// Use in a form
export default function NewPostForm() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" />
<button type="submit">Publish</button>
</form>
);
}
Manipuladores de rota (rotas de API)
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const page = parseInt(searchParams.get("page") ?? "1");
const users = await db.users.findMany({
skip: (page - 1) * 10,
take: 10,
});
return NextResponse.json({ users, page });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const user = await db.users.create({ data: body });
return NextResponse.json(user, { status: 201 });
}
// app/api/users/[id]/route.ts
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
await db.users.delete({ where: { id } });
return new NextResponse(null, { status: 204 });
}
Pré-renderização parcial (PPR)
PPR permite combinar conteúdo estático e dinâmico em uma única página:
// next.config.ts
import type { NextConfig } from "next";
const config: NextConfig = {
experimental: {
ppr: true,
},
};
export default config;
// page.tsx — static shell + dynamic island
import { Suspense } from "react";
export default function ProductPage({ params }: { params: { id: string } }) {
return (
<div>
{/* Static — generated at build time */}
<ProductDetails id={params.id} />
{/* Dynamic — rendered per request */}
<Suspense fallback={<PriceSkeleton />}>
<DynamicPrice id={params.id} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<UserReviews id={params.id} />
</Suspense>
</div>
);
}
Middleware
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("auth-token")?.value;
const isProtected = request.nextUrl.pathname.startsWith("/dashboard");
if (isProtected && !token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Add security headers
const response = NextResponse.next();
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-Content-Type-Options", "nosniff");
return response;
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
Otimização de imagem
import Image from "next/image";
// Optimized image — auto WebP/AVIF, lazy loading, size hints
export function HeroImage({ src, alt }: { src: string; alt: string }) {
return (
<Image
src={src}
alt={alt}
width={1200}
height={630}
priority // LCP image — eager load
quality={85}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j..." // low-quality placeholder
/>
);
}
// Fill container
<div style={{ position: "relative", aspectRatio: "16/9" }}>
<Image src="/hero.jpg" alt="Hero" fill sizes="100vw" />
</div>
Implantação e Produção
- Vercel— configuração zero, rede de borda, PPR automático
- Auto-hospedado —
next build && next startcom Node.js - Docker– Dockerfile Next.js oficial com saída independente
Next.js 15 é a estrutura React mais completa para 2026. Os componentes do servidor eliminam a busca excessiva, as ações do servidor simplificam as mutações e o PPR oferece o melhor em renderização estática e dinâmica na mesma página.
🔗 Share this article
✍️ Leave a Comment