Next.js 15 Lançado - Estabilização do Turbopack e Suporte ao React 19

2025.12.02

Documentação Oficial

Visão Geral do Next.js 15

O Next.js 15 é um lançamento principal que traz suporte oficial ao React 19 e estabilização do Turbopack. Melhorias significativas foram feitas tanto na experiência de desenvolvimento quanto no desempenho.

flowchart TB
    subgraph Next15["Destaques do Next.js 15"]
        subgraph Turbo["Turbopack (Estável para Dev)"]
            T1["Inicialização do servidor dev: 76% mais rápido"]
            T2["Fast Refresh: 96% mais rápido"]
            T3["Compilação inicial: 45% mais rápido"]
        end

        subgraph React19["Suporte ao React 19"]
            R1["Server Actions (Estável)"]
            R2["use() Hook"]
            R3["React Compiler (Experimental)"]
        end

        subgraph PPR["Partial Prerendering (Beta)"]
            P1["Shell estático + Streaming de conteúdo dinâmico"]
        end

        subgraph Cache["Novos Padrões de Cache"]
            C1["fetch: no-store (padrão)"]
            C2["Route Handlers: no-store (padrão)"]
        end
    end

Estabilização do Turbopack

No Next.js 15, o Turbopack para o servidor de desenvolvimento tornou-se estável.

# Iniciar servidor de desenvolvimento com Turbopack
next dev --turbo

# Build ainda usa Webpack (Turbopack em desenvolvimento)
next build

Comparação de Desempenho

Comparação de desempenho em projeto de grande escala (5000 módulos):

ItemWebpackTurbopackMelhoria
Inicialização do servidor dev8,2s2,0s76% mais rápido
Fast Refresh (alteração de 1 arquivo)800ms30ms96% mais rápido
Compilação inicial de rota600ms330ms45% mais rápido
Uso de memória1,6GB800MB50% de redução

Suporte ao React 19

Server Actions

// 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;

  // Validação
  if (!title || title.length < 3) {
    return { error: 'O título deve ter pelo menos 3 caracteres' };
  }

  // Operação no banco de dados
  await db.posts.create({ title, content });

  // Revalidação do cache
  revalidatePath('/posts');

  // Redirecionamento
  redirect('/posts');
}

// Action com atualização otimista
export async function likePost(postId: string) {
  await db.posts.update({
    where: { id: postId },
    data: { likes: { increment: 1 } },
  });

  revalidatePath(`/posts/${postId}`);
}
// app/posts/new/page.tsx
import { createPost } from '../actions';

export default function NewPostPage() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Título" required />
      <textarea name="content" placeholder="Conteúdo" required />
      <button type="submit">Criar Post</button>
    </form>
  );
}

useActionState

'use client';

import { useActionState } from 'react';
import { createPost } from '../actions';

export function CreatePostForm() {
  const [state, formAction, isPending] = useActionState(createPost, null);

  return (
    <form action={formAction}>
      <input name="title" placeholder="Título" disabled={isPending} />
      <textarea name="content" placeholder="Conteúdo" disabled={isPending} />

      {state?.error && (
        <p className="text-red-500">{state.error}</p>
      )}

      <button type="submit" disabled={isPending}>
        {isPending ? 'Criando...' : 'Criar Post'}
      </button>
    </form>
  );
}

useOptimistic

'use client';

import { useOptimistic } from 'react';
import { likePost } from '../actions';

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

  async function handleLike() {
    addOptimisticLike(null);
    await likePost(postId);
  }

  return (
    <form action={handleLike}>
      <button type="submit">
        ❤️ {optimisticLikes}
      </button>
    </form>
  );
}

Partial Prerendering (PPR)

Uma nova estratégia de renderização que combina shell estático com conteúdo dinâmico.

flowchart TB
    subgraph BuildTime["Tempo de Build"]
        subgraph StaticShell["Shell Estático"]
            Header["Header (estático)"]
            subgraph Layout["Layout"]
                Content["Conteúdo (estático)"]
                Suspense["Suspense Fallback<br/>(Skeleton)"]
            end
        end
    end

    subgraph RequestTime["Tempo de Requisição"]
        Step1["1. Retorna shell estático imediatamente (TTFB: poucos ms)"]
        Step2["2. Streaming da parte dinâmica"]
        Step3["3. Boundaries do Suspense hidratam sequencialmente"]
        Step1 --> Step2 --> Step3
    end

    BuildTime --> RequestTime
// next.config.js
module.exports = {
  experimental: {
    ppr: true,
  },
};
// app/products/[id]/page.tsx
import { Suspense } from 'react';

// Parte gerada estaticamente
export default function ProductPage({ params }: Props) {
  return (
    <div>
      {/* Conteúdo estático */}
      <ProductDetails id={params.id} />

      {/* Conteúdo dinâmico (streaming) */}
      <Suspense fallback={<RecommendationsSkeleton />}>
        <PersonalizedRecommendations userId={getUserId()} />
      </Suspense>

      <Suspense fallback={<ReviewsSkeleton />}>
        <RecentReviews productId={params.id} />
      </Suspense>
    </div>
  );
}

// Componente que usa dados dinâmicos
async function PersonalizedRecommendations({ userId }: { userId: string }) {
  // Esta parte é executada no tempo de requisição
  const recommendations = await getRecommendations(userId);
  return <RecommendationList items={recommendations} />;
}

Nova Semântica de Cache

No Next.js 15, o comportamento padrão do cache foi alterado.

// Next.js 14 e anteriores: Cache por padrão
// Next.js 15: Sem cache por padrão

// Mudança no padrão da API fetch
async function getData() {
  // Next.js 14: cache: 'force-cache' era o padrão
  // Next.js 15: cache: 'no-store' é o padrão
  const res = await fetch('https://api.example.com/data');
  return res.json();
}

// Especificar explicitamente para habilitar o cache
async function getCachedData() {
  const res = await fetch('https://api.example.com/data', {
    cache: 'force-cache',
    next: { revalidate: 3600 }, // 1 hora
  });
  return res.json();
}

Cache dos Route Handlers

// app/api/data/route.ts

// Next.js 14: GET era cacheado estaticamente
// Next.js 15: Sem cache por padrão

// Habilitar cache
export const dynamic = 'force-static';
// ou
export const revalidate = 3600;

export async function GET() {
  const data = await fetchData();
  return Response.json(data);
}

Melhorias de Segurança

Segurança das Server Actions

// Proteção de endpoint das Server Actions
// next.config.js
module.exports = {
  experimental: {
    serverActions: {
      // Especificar origens permitidas
      allowedOrigins: ['my-app.com', 'staging.my-app.com'],
    },
  },
};
// app/actions.ts
'use server';

import { headers } from 'next/headers';

export async function sensitiveAction() {
  // Verificação de referer
  const referer = headers().get('referer');
  if (!referer?.startsWith('https://my-app.com')) {
    throw new Error('Origem inválida');
  }

  // Proteção CSRF é feita automaticamente
  // ...
}

Melhorias na Exportação Estática

// next.config.js
module.exports = {
  output: 'export',

  // Otimização de imagens na exportação estática
  images: {
    unoptimized: true,
    // ou usar loader externo
    loader: 'custom',
    loaderFile: './image-loader.js',
  },
};
// image-loader.js
export default function customLoader({ src, width, quality }) {
  return `https://cdn.example.com/${src}?w=${width}&q=${quality || 75}`;
}

instrumentation.ts

Permite executar código durante o bootstrap da aplicação.

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    // Executar apenas no runtime Node.js
    await import('./monitoring/sentry');
  }

  if (process.env.NEXT_RUNTIME === 'edge') {
    // Executar apenas no runtime Edge
    await import('./monitoring/edge-logger');
  }
}

export function onRequestError(error: Error, request: Request) {
  // Envio de logs de erro
  console.error('Erro na requisição:', error);
}

Migração

# Upgrade
npm install next@15 react@19 react-dom@19

# Codemod automático
npx @next/codemod@latest upgrade latest

Principais Mudanças Quebradas

MudançaNext.js 14Next.js 15
fetch padrãocache: ‘force-cache’cache: ‘no-store’
Route HandlerCache estáticoSem cache
Versão do ReactReact 18React 19
Node.js mínimo18.17.018.18.0
← Voltar para a lista