Voltar para o blog
Desenvolvimento Web

Next.js 15: Novidades e Melhores Práticas

Explore as novas features do Next.js 15 e aprenda a construir aplicações modernas e performáticas

Foto de Vinicius Mendes
Especialista em IA Aplicada | Full Stack Engineer | UX/UI Designer
18 min de leitura
Imagem de capa do artigo: Next.js 15: Novidades e Melhores Práticas

Next.js 15: A Nova Era do Desenvolvimento Web

O Next.js 15 representa um salto significativo no ecossistema React, trazendo melhorias de performance, novas APIs e recursos que simplificam o desenvolvimento de aplicações web modernas. Neste guia completo, vamos explorar todas as novidades e como aplicá-las em projetos reais.

Next.js 15 - Framework React de última geração

O que há de novo no Next.js 15?

O Next.js 15 traz diversas melhorias significativas:

Principais Novidades

Feature Descrição Impacto
Turbopack Stable Bundler ultra-rápido 10x mais rápido
React 19 Support Novas features do React Actions, Compiler
Async Request APIs await cookies/headers Breaking change
Partial Prerendering Static + Dynamic Performance
Enhanced Caching Novo modelo de cache Mais controle

"Next.js 15 não é apenas uma atualização, é uma reimaginação de como construímos aplicações web." - Guillermo Rauch, CEO Vercel

Instalação e Migração

Novo Projeto

bash
# Criar novo projeto Next.js 15
npx create-next-app@latest my-app --typescript --tailwind --eslint

# Opções recomendadas:
# ✔ Would you like to use App Router? Yes
# ✔ Would you like to customize the default import alias? Yes (@/*)

Migração de Projeto Existente

bash
# Atualizar dependências
npm install next@latest react@latest react-dom@latest

# Ou com codemod automático
npx @next/codemod@latest upgrade latest

Turbopack: O Novo Bundler

O Turbopack agora é estável para desenvolvimento e oferece performance incrível:

bash
# Habilitar Turbopack
next dev --turbo

# Ou no package.json
{
  "scripts": {
    "dev": "next dev --turbo"
  }
}

Benchmarks de Performance

Métrica Webpack Turbopack Melhoria
Cold Start 8.2s 1.1s 7.5x
Fast Refresh 500ms 50ms 10x
Build Time 45s 12s 3.7x

Configuração Avançada

typescript
// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  experimental: {
    turbo: {
      rules: {
        '*.svg': {
          loaders: ['@svgr/webpack'],
          as: '*.js',
        },
      },
    },
  },
};

export default nextConfig;

Async Request APIs

Uma das mudanças mais importantes: APIs de request agora são assíncronas.

Antes (Next.js 14)

typescript
// ❌ Forma antiga - síncrona
import { cookies, headers } from 'next/headers';

export default function Page() {
  const cookieStore = cookies();
  const headersList = headers();

  return <div>...</div>;
}

Depois (Next.js 15)

typescript
// ✅ Forma nova - assíncrona
import { cookies, headers } from 'next/headers';

export default async function Page() {
  const cookieStore = await cookies();
  const headersList = await headers();

  const theme = cookieStore.get('theme')?.value;
  const userAgent = headersList.get('user-agent');

  return (
    <div>
      <p>Theme: {theme}</p>
      <p>User Agent: {userAgent}</p>
    </div>
  );
}

Params e SearchParams também são async

typescript
// app/blog/[slug]/page.tsx
interface Props {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}

export default async function BlogPost({ params, searchParams }: Props) {
  const { slug } = await params;
  const { page = '1' } = await searchParams;

  const post = await getPost(slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <p>Page: {page}</p>
    </article>
  );
}

Server Actions Aprimorados

Server Actions ficaram ainda mais poderosos no Next.js 15:

Definindo Server Actions

typescript
// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';

// Schema de validação
const CreatePostSchema = z.object({
  title: z.string().min(3).max(100),
  content: z.string().min(10),
  published: z.boolean().default(false),
});

// Tipo para o estado do formulário
type FormState = {
  success: boolean;
  message: string;
  errors?: {
    title?: string[];
    content?: string[];
  };
};

export async function createPost(
  prevState: FormState,
  formData: FormData
): Promise<FormState> {
  // Validar dados
  const validatedFields = CreatePostSchema.safeParse({
    title: formData.get('title'),
    content: formData.get('content'),
    published: formData.get('published') === 'on',
  });

  if (!validatedFields.success) {
    return {
      success: false,
      message: 'Erro de validação',
      errors: validatedFields.error.flatten().fieldErrors,
    };
  }

  try {
    // Salvar no banco
    await db.post.create({
      data: validatedFields.data,
    });

    // Revalidar cache
    revalidatePath('/posts');

    return {
      success: true,
      message: 'Post criado com sucesso!',
    };
  } catch (error) {
    return {
      success: false,
      message: 'Erro ao criar post',
    };
  }
}

Usando com useActionState (React 19)

typescript
// app/posts/new/page.tsx
'use client';

import { useActionState } from 'react';
import { createPost } from '@/app/actions';

const initialState = {
  success: false,
  message: '',
};

export default function NewPostForm() {
  const [state, formAction, isPending] = useActionState(
    createPost,
    initialState
  );

  return (
    <form action={formAction} className="space-y-4">
      <div>
        <label htmlFor="title" className="block font-medium">
          Título
        </label>
        <input
          type="text"
          id="title"
          name="title"
          className="w-full border rounded-lg p-2"
          disabled={isPending}
        />
        {state.errors?.title && (
          <p className="text-red-500 text-sm">{state.errors.title[0]}</p>
        )}
      </div>

      <div>
        <label htmlFor="content" className="block font-medium">
          Conteúdo
        </label>
        <textarea
          id="content"
          name="content"
          rows={5}
          className="w-full border rounded-lg p-2"
          disabled={isPending}
        />
        {state.errors?.content && (
          <p className="text-red-500 text-sm">{state.errors.content[0]}</p>
        )}
      </div>

      <button
        type="submit"
        disabled={isPending}
        className="bg-blue-500 text-white px-4 py-2 rounded-lg disabled:opacity-50"
      >
        {isPending ? 'Criando...' : 'Criar Post'}
      </button>

      {state.message && (
        <p className={state.success ? 'text-green-500' : 'text-red-500'}>
          {state.message}
        </p>
      )}
    </form>
  );
}

Partial Prerendering (PPR)

O Partial Prerendering combina o melhor de SSG e SSR na mesma página:

Partial Prerendering - Static shell com streaming dinâmico

Habilitando PPR

typescript
// next.config.ts
const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental', // ou true para todas as páginas
  },
};

Usando PPR na Página

typescript
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { DashboardSkeleton } from '@/components/skeletons';
import { DynamicStats } from '@/components/dynamic-stats';

// Habilitar PPR para esta rota
export const experimental_ppr = true;

// Parte estática - pré-renderizada no build
function StaticHeader() {
  return (
    <header className="bg-white shadow">
      <h1 className="text-2xl font-bold">Dashboard</h1>
      <nav>...</nav>
    </header>
  );
}

// Parte dinâmica - streamed no request
async function DynamicContent() {
  const stats = await fetchRealTimeStats();
  return <DynamicStats data={stats} />;
}

export default function DashboardPage() {
  return (
    <div>
      {/* Static shell - entregue instantaneamente */}
      <StaticHeader />

      {/* Dynamic content - streamed depois */}
      <Suspense fallback={<DashboardSkeleton />}>
        <DynamicContent />
      </Suspense>
    </div>
  );
}

Como funciona?

  1. Build time: Shell estático é pré-renderizado
  2. Request time: Shell é servido instantaneamente do edge
  3. Streaming: Conteúdo dinâmico é streamed assim que pronto

Novo Modelo de Caching

O Next.js 15 introduz mudanças importantes no sistema de cache:

Mudanças Principais

API Next.js 14 Next.js 15
fetch() Cached por padrão Não cached
GET Route Handlers Cached Não cached
Client Router Cached 30s Não cached

Configurando Cache Explicitamente

typescript
// Cache explícito para fetch
const data = await fetch('https://api.example.com/data', {
  cache: 'force-cache', // Habilitar cache
  next: {
    revalidate: 3600, // Revalidar a cada hora
    tags: ['posts'], // Tag para revalidação seletiva
  },
});

// Ou sem cache
const liveData = await fetch('https://api.example.com/live', {
  cache: 'no-store',
});

unstable_cache para Funções

typescript
import { unstable_cache } from 'next/cache';

const getCachedPosts = unstable_cache(
  async () => {
    const posts = await db.post.findMany();
    return posts;
  },
  ['posts'], // Cache key
  {
    revalidate: 3600, // 1 hora
    tags: ['posts'],
  }
);

// Revalidar por tag
import { revalidateTag } from 'next/cache';
revalidateTag('posts');

React 19 no Next.js 15

O Next.js 15 vem com suporte completo ao React 19:

useOptimistic

typescript
'use client';

import { useOptimistic } from 'react';
import { likePost } from '@/app/actions';

export function LikeButton({ postId, initialLikes }: Props) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    initialLikes,
    (state, newLike: number) => state + newLike
  );

  async function handleLike() {
    addOptimisticLike(1); // UI atualiza imediatamente
    await likePost(postId); // Server action em background
  }

  return (
    <button onClick={handleLike}>
      ❤️ {optimisticLikes}
    </button>
  );
}

use() Hook

typescript
'use client';

import { use } from 'react';

export function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  // Suspense boundary necessário acima
  const comments = use(commentsPromise);

  return (
    <ul>
      {comments.map(comment => (
        <li key={comment.id}>{comment.text}</li>
      ))}
    </ul>
  );
}

Otimizações de Performance

next/image Melhorado

typescript
import Image from 'next/image';

export function OptimizedImage() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={630}
      priority // Para imagens above-the-fold
      placeholder="blur" // Blur placeholder
      blurDataURL="data:image/..." // Base64 blur
      sizes="(max-width: 768px) 100vw, 50vw"
    />
  );
}

Font Optimization

typescript
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
});

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
});

export default function RootLayout({ children }) {
  return (
    <html lang="pt-BR" className={`${inter.variable} ${robotoMono.variable}`}>
      <body>{children}</body>
    </html>
  );
}

Checklist de Migração

Antes de Migrar

  • Fazer backup do projeto
  • Criar branch de migração
  • Atualizar Node.js para 18.17+
  • Revisar breaking changes na documentação

Durante a Migração

  • Atualizar dependências (next, react, react-dom)
  • Rodar codemods automatizados
  • Converter APIs síncronas para async
  • Atualizar estratégias de cache
  • Testar todas as rotas

Depois da Migração

  • Verificar performance com Lighthouse
  • Testar em diferentes dispositivos
  • Monitorar erros em produção

Conclusão

O Next.js 15 é uma atualização transformadora que prepara o terreno para o futuro do desenvolvimento web com React. As novas APIs assíncronas, o Turbopack estável e o PPR representam avanços significativos em performance e DX.

Recursos Adicionais

Documentação:

Comunidade:

Última atualização: Dezembro 2024. Este guia será atualizado conforme novas features forem lançadas.

Tags

  • Next.js
  • React
  • TypeScript
  • Server Actions
  • Performance
  • Turbopack
  • PPR
  • React 19
  • Vercel

Continue explorando outros conteúdos que podem te interessar