Next.js vs Nuxt Performance: Real Benchmarks and When Each Wins

You've read ten "Next.js vs Nuxt" articles that all say "it depends." Frustrating, right?

Here's the thing: they're correct—but they never tell you what it depends on. This guide does. We'll compare real benchmark data, show you exactly where each framework wins, and give you a clear decision framework for your next project.

The short answer (with numbers)

Neither framework is universally faster. The benchmarks tell a more interesting story:

WorkloadWinnerMargin
Simple SSR pagesNext.js+87% faster
Pages with API callsNuxt+144% faster
Edge cold startsNuxt~2ms vs ~30ms
Bundle size (baseline)Nuxt~33% smaller

The framework you choose matters less than how you use it. But if you know your workload, you can pick the one optimized for it.

The benchmark data (real numbers)

Enough hand-waving. Here are actual requests-per-second measurements for server-side rendering:

ScenarioNuxt 3Next.jsDifference
Simple "Hello World"1,376 req/s2,570 req/sNext.js +87%
Rendering a component1,447 req/s2,794 req/sNext.js +93%
Page with API fetch947 req/s388 req/sNuxt +144%

What this means:

Next.js's raw rendering speed is faster for simple pages. But most real apps fetch data, and Nuxt handles that workload significantly better.

Why the difference? Nuxt's Nitro server is optimized for data fetching patterns. Next.js's strength is in raw React rendering speed, but that advantage disappears when I/O is involved.

Build and dev server speed

Development experience matters. Slow builds kill productivity and make developers avoid running the dev server.

Nuxt: Vite-powered

Nuxt 3 uses Vite, which leverages native ES modules for near-instant hot module replacement. Unlike traditional bundlers, Vite doesn't bundle your code during development - it serves ES modules directly to the browser.

// nuxt.config.ts
export default defineNuxtConfig({
  // Vite is the default - no config needed
  // HMR is nearly instant out of the box
  vite: {
    // Optional: customize Vite config
    optimizeDeps: {
      include: ['some-cjs-dependency'],
    },
  },
})

Nuxt/Vite development characteristics:

  • Cold start: 300-800ms for typical apps
  • HMR: Usually under 100ms
  • Memory usage: Lower than Webpack-based setups
  • Nuxt claims 80% faster HMR than Webpack-based Next.js

The tradeoff: Vite's unbundled approach means your browser makes hundreds of HTTP requests during development. This is fine locally but can feel slow over poor network connections (like remote development servers).

Next.js: Turbopack

Next.js is transitioning from Webpack to Turbopack, a Rust-based bundler written by the same team that created Webpack:

// next.config.js
module.exports = {
  // Turbopack is now stable for dev
  // Enable with: next dev --turbo
}

Turbopack characteristics:

  • Cold start: 200-500ms (faster than Vite for large apps)
  • HMR: Sub-100ms with incremental compilation
  • Memory usage: Efficient due to Rust's memory management
  • Particularly fast for large monorepos (1000+ modules)

Turbopack's incremental computation model means it only recomputes what changed. For large applications with complex dependency graphs, this can be dramatically faster than both Vite and Webpack.

# Enable Turbopack
next dev --turbo

# You'll see in the console:
# ▲ Next.js 15.x (turbopack)
# - Local: http://localhost:3000

Production build comparison

MetricNuxt (Vite/Rollup)Next.js (Turbopack/Webpack)
Small app build5-15s10-30s
Large app build30-60s45-90s
Incremental rebuildFastFast with Turbopack
Tree-shakingExcellent (Rollup)Good (improving)

The verdict

For most projects, both are fast enough that you won't notice a difference. For enterprise-scale monorepos with thousands of modules, Turbopack has demonstrated faster cold starts and HMR. For typical projects, Vite's mature ecosystem, plugin library, and stability make it a safe choice.

If you're starting fresh and prioritize build speed, both frameworks are competitive. Choose based on React vs Vue preference, not build tools.

Bundle size comparison

Bundle size directly affects LCP - smaller bundles mean faster page loads.

Framework runtime size

Vue's runtime is roughly 33% smaller than React's:

FrameworkMinified + Gzipped
Vue 3~33 KB
React 18 + ReactDOM~44 KB

This baseline difference means Nuxt apps start with a smaller JavaScript footprint.

But Server Components change the equation

Next.js's React Server Components can dramatically reduce client-side JavaScript by keeping components on the server:

// Next.js - This component ships ZERO JS to the client
async function ProductList() {
  const products = await db.products.findMany();

  return (
    <ul>
      {products.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

When used effectively, RSC can make Next.js bundles smaller than equivalent Nuxt apps despite React's larger runtime.

Nuxt's auto-imports

Nuxt's auto-import system uses build-time analysis to tree-shake aggressively:

<script setup>
// No imports needed - Nuxt handles it at build time
// Only what you use gets bundled
const { data } = await useFetch('/api/products')
</script>

This "magic" is analyzed statically, so it doesn't hurt bundle size.

The verdict

Out of the box, Nuxt produces smaller bundles. With disciplined use of Server Components, Next.js can match or beat Nuxt. Both require attention to achieve optimal bundle sizes.

Image optimization

Images are usually the biggest LCP factor. How each framework handles them matters.

Next.js: Built-in

Next.js has native image optimization:

import Image from 'next/image';

function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero"
      width={1200}
      height={600}
      priority // Marks as LCP image
    />
  );
}

Features included out of the box:

  • Automatic WebP/AVIF conversion
  • Responsive srcset generation
  • Lazy loading by default
  • Blur placeholder support
  • Vercel's edge network optimization

Nuxt: Module required

Nuxt needs the @nuxt/image module:

npm install @nuxt/image
<template>
  <NuxtImg
    src="/hero.jpg"
    alt="Hero"
    width="1200"
    height="600"
    preload
  />
</template>

Once installed, it's equally capable - modern formats, responsive images, lazy loading. But it's an extra setup step.

The verdict

Next.js wins on convenience. Image optimization just works. Nuxt can match it, but you need to install and configure the module.

Server Components

Server Components are the biggest architectural difference between the frameworks.

Next.js: React Server Components (Mature)

RSC is the default in Next.js App Router. Components run on the server unless you opt into client-side:

// Server Component (default) - no JS sent to client
async function Dashboard() {
  const data = await fetchDashboardData();
  return <DashboardView data={data} />;
}

// Client Component - opt in with directive
'use client';
function InteractiveChart({ data }) {
  const [zoom, setZoom] = useState(1);
  return <Chart data={data} zoom={zoom} onZoom={setZoom} />;
}

RSC enables:

  • Direct database access in components
  • Streaming and Suspense
  • Granular client/server boundaries
  • Significant bundle size reduction

Nuxt: Server Components (Experimental)

Nuxt has server components, but they work differently:

<!-- components/ServerOnly.server.vue -->
<script setup>
// This component renders on server only
const data = await $fetch('/api/data')
</script>

<template>
  <div>{{ data }}</div>
</template>

Nuxt's approach is simpler but less flexible. For most Vue patterns, you'll use useFetch and useAsyncData instead:

<script setup>
// Runs on server during SSR, cached for client
const { data } = await useFetch('/api/products')
</script>

The verdict

Next.js has more mature and flexible server component patterns. Nuxt's data fetching primitives (useFetch, useAsyncData) handle most use cases elegantly without needing explicit server components.

Edge performance

Edge computing serves pages from servers closest to users. Instead of one central server in Virginia, your code runs in 200+ locations worldwide. This reduces latency from 100-200ms to 10-30ms for most users.

Nuxt: Nitro's edge advantage

Nitro, Nuxt's server engine, was designed from the ground up for edge deployment. It compiles your entire Nuxt application into a minimal, self-contained bundle that runs anywhere.

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: 'cloudflare-pages' // or 'vercel-edge', 'netlify-edge', 'deno-deploy'
  }
})

Nitro's edge characteristics:

MetricNitro on Cloudflare Workers
Cold start~2ms
Bundle size~700 KB for full app
Memory limitWorks within 128MB
Supported platforms15+ (Cloudflare, Vercel, Netlify, Deno, AWS Lambda, etc.)

The ~2ms cold start is remarkable. Traditional serverless functions take 100-500ms to cold start. Nitro achieves this by:

  • Compiling to optimized JavaScript (no Node.js runtime needed)
  • Bundling dependencies inline (no npm install at runtime)
  • Using the Web API standard (works natively on edge runtimes)
// Nitro compiles this to a single, optimized bundle
export default defineEventHandler(async (event) => {
  const data = await $fetch('/api/products');
  return { products: data };
});

Multi-platform flexibility:

Nitro's biggest advantage is deployment flexibility. The same Nuxt app can deploy to any edge platform without code changes:

# Deploy to Cloudflare
NITRO_PRESET=cloudflare-pages npm run build

# Deploy to Vercel Edge
NITRO_PRESET=vercel-edge npm run build

# Deploy to Netlify Edge
NITRO_PRESET=netlify-edge npm run build

Next.js: Vercel Edge

Next.js edge functions are optimized for Vercel's infrastructure:

// app/api/hello/route.js
export const runtime = 'edge';

export async function GET() {
  return Response.json({ hello: 'world' });
}

// For pages
// app/page.js
export const runtime = 'edge';

export default function Page() {
  return <div>Edge-rendered page</div>;
}

Next.js Edge characteristics:

MetricNext.js on Vercel Edge
Cold start~10-30ms
Bundle sizeVaries by page
Memory limit128MB on Vercel
Supported platformsBest on Vercel, limited elsewhere

Next.js edge is fast but not quite Nitro-level for cold starts. However, Vercel's infrastructure compensates with:

  • Aggressive function warming (keeping instances hot)
  • Geographic routing optimization
  • Integrated caching with the Next.js data cache

The platform lock-in consideration:

Next.js edge functions use some Vercel-specific APIs. Deploying to other platforms (Cloudflare, Netlify) requires adapters and may not support all features. This isn't a problem if you're on Vercel, but matters for multi-cloud strategies.

When edge matters

Edge deployment significantly improves performance for:

  1. Global audiences: Users in Asia see 100ms+ latency improvements when served from Singapore vs Virginia.
  2. Personalized content: Server-render personalized pages at the edge without client-side JavaScript.
  3. API routes: Database queries from edge locations closest to your database regions.
  4. A/B testing: Run experiments at the edge without client-side flicker.

When edge doesn't matter:

  • Static sites (use a CDN instead)
  • Apps with a single geographic audience
  • Heavy computation (edge has CPU limits)
  • Large dependencies (edge bundles have size limits)

The verdict

Nuxt/Nitro has faster edge cold starts (~2ms vs ~10-30ms) and better multi-platform support. For latency-critical applications deployed outside Vercel, Nuxt has a clear advantage.

On Vercel, Next.js's deeper integration (caching, analytics, function warming) may offset the cold start difference. If you're already committed to Vercel, Next.js edge works excellently.

For multi-cloud or vendor-neutral deployments, Nitro's universal preset system makes Nuxt the safer choice.

Core Web Vitals optimization

Here's how to optimize each framework for LCP, CLS, and INP. The techniques are framework-specific, but the principles are the same.

LCP optimization

LCP measures when your largest content element becomes visible. For both frameworks, the main factors are: image optimization, server rendering, and JavaScript bundle size.

Next.js approach:

Next.js has built-in image optimization that handles most LCP issues automatically:

import Image from 'next/image';

function HeroSection() {
  return (
    <section>
      {/* priority tells Next.js this is the LCP image */}
      {/* It disables lazy loading and adds preload hints */}
      <Image
        src="/hero.jpg"
        alt="Hero"
        width={1200}
        height={600}
        priority
      />
      <h1>Welcome to our site</h1>
    </section>
  );
}

Additional Next.js LCP strategies:

  • Use Server Components (default in App Router) to reduce client JS
  • Enable streaming with loading.js for faster TTFB
  • Use next/font to eliminate font-loading delays
// Streaming shows content progressively
// app/page.js
import { Suspense } from 'react';

export default function Page() {
  return (
    <main>
      <HeroSection /> {/* Shows immediately */}
      <Suspense fallback={<ProductsSkeleton />}>
        <ProductList /> {/* Streams in when ready */}
      </Suspense>
    </main>
  );
}

Nuxt approach:

Nuxt requires the @nuxt/image module for equivalent optimization:

<template>
  <section>
    <!-- preload tells Nuxt this is the LCP image -->
    <NuxtImg
      src="/hero.jpg"
      alt="Hero"
      width="1200"
      height="600"
      preload
    />
    <h1>Welcome to our site</h1>
  </section>
</template>

<script setup>
// Data fetched server-side, HTML sent immediately
const { data: hero } = await useFetch('/api/hero', {
  server: true,  // Only runs on server
  lazy: false,   // Blocks rendering until complete
})
</script>

Additional Nuxt LCP strategies:

  • Use useFetch with server: true for SSR data
  • Lazy load below-fold components with Lazy prefix
  • Use @nuxtjs/fontaine for font fallback matching

CLS optimization

CLS measures visual stability. Both frameworks handle this similarly - the key is always specifying dimensions for media elements.

The universal rule: Every image, video, iframe, and dynamic container needs explicit dimensions.

// Next.js - Image component requires dimensions
<Image src="/photo.jpg" width={800} height={600} alt="Photo" />

// For responsive images, use fill with a sized container
<div style={{ position: 'relative', width: '100%', aspectRatio: '16/9' }}>
  <Image src="/photo.jpg" fill alt="Photo" />
</div>
<!-- Nuxt - Same principle -->
<NuxtImg src="/photo.jpg" width="800" height="600" alt="Photo" />

<!-- Responsive with aspect ratio -->
<div class="image-container">
  <NuxtImg src="/photo.jpg" fit="cover" alt="Photo" />
</div>

<style>
.image-container {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
}
</style>

Framework-specific CLS gotchas:

IssueNext.js SolutionNuxt Solution
Font loadingUse next/font with display: swapUse @nuxtjs/fontaine
Dynamic contentUse Suspense with sized fallbacksUse v-if with sized placeholders
Ads/embedsReserve space with CSSReserve space with CSS

INP optimization

INP measures how quickly your app responds to user interactions. This is where React and Vue have different patterns.

Next.js (React) approach:

React 18+ provides useTransition for non-urgent updates:

'use client';
import { useState, useTransition } from 'react';

function SearchResults({ items }) {
  const [query, setQuery] = useState('');
  const [filtered, setFiltered] = useState(items);
  const [isPending, startTransition] = useTransition();

  function handleSearch(value) {
    // Input updates immediately (urgent)
    setQuery(value);

    // Filtering happens in background (non-urgent)
    startTransition(() => {
      setFiltered(items.filter(i => i.name.includes(value)));
    });
  }

  return (
    <div>
      <input
        value={query}
        onChange={e => handleSearch(e.target.value)}
      />
      <div style={{ opacity: isPending ? 0.7 : 1 }}>
        {filtered.map(item => <Item key={item.id} {...item} />)}
      </div>
    </div>
  );
}

Nuxt (Vue) approach:

Vue doesn't have useTransition, but you can achieve similar results with debouncing and nextTick:

<template>
  <div>
    <input v-model="query" @input="handleSearch" />
    <div :style="{ opacity: isPending ? 0.7 : 1 }">
      <Item v-for="item in filtered" :key="item.id" v-bind="item" />
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import { useDebounceFn } from '@vueuse/core';

const query = ref('');
const debouncedQuery = ref('');
const isPending = ref(false);
const items = defineProps(['items']);

const filtered = computed(() =>
  items.filter(i => i.name.includes(debouncedQuery.value))
);

const handleSearch = useDebounceFn(() => {
  isPending.value = true;
  // Use requestAnimationFrame for visual feedback
  requestAnimationFrame(() => {
    debouncedQuery.value = query.value;
    isPending.value = false;
  });
}, 150);
</script>

Which is better for INP?

React's useTransition is more elegant and integrated into the framework. Vue's approach requires external utilities but offers more explicit control. In practice, both achieve similar INP scores when implemented correctly.

The verdict

Both frameworks can achieve excellent Core Web Vitals scores. The differences are in ergonomics, not capability:

  • Next.js advantages: Built-in image optimization, native useTransition for INP, streaming SSR out of the box
  • Nuxt advantages: Smaller baseline bundle, simpler data fetching patterns, font optimization via modules

Framework choice doesn't determine your scores - implementation does. A well-optimized Nuxt site will outperform a poorly optimized Next.js site, and vice versa.

Decision guide

Choose Next.js if:

  • Your team knows React
  • You're deploying to Vercel
  • You need mature Server Component patterns
  • You're building a large-scale app with complex data requirements
  • You want built-in image optimization without setup

Choose Nuxt if:

  • Your team knows Vue
  • You need multi-cloud deployment flexibility
  • You're building content-driven sites
  • You want smaller bundles out of the box
  • Edge cold start time is critical

Either works well for:

  • E-commerce sites
  • Marketing sites
  • Dashboards
  • Content management
  • API-driven applications

Quick comparison table

AspectNext.jsNuxt 3
Base frameworkReactVue
SSR throughput (simple)Faster (+87%)Slower
SSR throughput (API fetch)SlowerFaster (+144%)
Bundle size (baseline)LargerSmaller (~33% less)
Server ComponentsMature, defaultExperimental
Image optimizationBuilt-inRequires module
Build toolTurbopack (Rust)Vite
Edge cold start~10-30ms~2ms
DeploymentBest on VercelFlexible (Nitro)
Core Web VitalsExcellentExcellent

The bottom line

Pick your framework based on your team's expertise and deployment needs—not performance benchmarks. Both Next.js and Nuxt can achieve sub-2-second LCP and passing Core Web Vitals when configured properly.

What actually moves the needle:

  1. Image optimization — biggest LCP impact, easiest win
  2. Code splitting — lazy load what users don't need immediately
  3. Server rendering — faster TTFB, better SEO
  4. Reserved space — prevent layout shifts with explicit dimensions

Framework-specific guides:

Already built your app and wondering where you're losing points? Run it through PageSpeedFix to identify exactly what's hurting your Core Web Vitals—we'll tell you whether it's framework-related or not.