Dewdew logo-mobile
Uses
Tech
Guestbook
Dewdew Dev

The Future of Frontend Technology: Comparing Nuxt and Next

Sub: Exploring API Similarities and Differences

nuxt4 nuxt vue3 typescript frontend framework meta framework nuxt4 blog nextjs nextjs blog nextjs feature
dewdew

Dewdew

Aug 29, 2025

10 min read

cover

Today, I’ll compare the two modern meta frameworks, Nuxt4 and Next,
and explore the similarities and differences in their APIs.

Before we start,

“What tool should I use to implement the service?”

Developers often think about what tools to use before implementing a product.
The decision is often made based on which framework supports the most libraries or packages that help implement the product easily.

As I develop, I’ve learned that this tool selection is highly dependent on the team’s preferences and the decision-maker’s technical decisions.
Then, should we always use Next only to develop our technology?

I’ll start writing this article from this question.
(I’m sure there are some prejudices in my article, but I’ll try to overcome them!)

In this way, in this article, I’ll explore the similarities and differences in the APIs provided by the two meta frameworks, Nuxt4 and Next,
and I’ll also discuss the future of frontend technology.
(I don’t know what’s ahead, but I’ll try to discuss it!)

Rendering Mode

First, I’ll explain the most basic rendering mode of the two meta frameworks.
Although the basic rendering mode of the two meta frameworks is similar, there are clear differences,
so I’ll enumerate the similarities and differences.

Basic Rendering Mode

Nuxt4 supports a Universal rendering mode, which is a SSR-based rendering mode that allows flexible CSR usage,
while Next.js has SSR mode as its default.
Under the assumption of basic settings, both frameworks operate as SSR as shown below.

Rendering Mode Diagram

Commonly, there is a way to set up a component that is only rendered on the client side.
By setting it up as shown below, you can use Client Side Component!

🟢 Nuxt4

// 1. Define a Client Component by adding `client` to the component name
// ex) CustomButton.client.vue

// 1-1. Define a Client Component by adding <Client-Only></Client-Only> to the template area
// ex) CustomButton.vue

<script setup lang="ts">
const count = ref(0)

const countUp = () => {
  count++
}
</script>

<template>
  <ClientOnly>
	<button @click="countUp()">
		{{ Count: `${count}` }}
	</button>
  </ClientOnly>
</template>


// It is also possible to define a component that is only rendered on the server side by adding `server` to the component name
// ex) CustomInput.server.vue

🔵 Next.js

// In Next.js, you can define a component that is only rendered on the client side by adding `use client` to the top of the .tsx file

'use client'

import { useState } from 'react'

export default const Count() => {
  const [count, countUp] = useState(0)
}
..
//

As shown below, Nuxt4 allows you to easily adjust the rendering mode for each route through config settings.
Nuxt calls this Hybrid Rendering.

<script setup lang="ts">
  export default defineNuxtConfig({
    routeRules: {
    // The root route is pre-rendered at build time
    '/': { prerender: true },
    // Handle redirection to a new URL
      '/pre-page': { redirect: '/next-page' },
    // All sub-routes of the management page are rendered as CSR
    '/admin/**': { ssr: false },
    // Create on demand: revalidate in the background until the API response changes
    '/products': { swr: true },
    // Create on demand: revalidate in the background until the API response changes
    '/products/**': { swr: 3600 },
    // Create on demand: revalidate in the background until the API response changes
    '/blog/**': { isr: true },
    // Create on demand: revalidate in the background until the API response changes
    '/blog': { isr: 3600 }
    }
})
</script>

APIs with the same name and function

As you can see, Nuxt and Next are moving similarly from the basic rendering structure.
However, beyond that, we’ll explore APIs with the same function and even the same name (or nearly similar)! The APIs I’ll introduce next are purely framework-provided functions, but I’ll also explore them to compare the two frameworks more closely!

1. Suspense

In both frameworks, Suspense is used to handle the waiting process for asynchronous operations. This allows components to display a Fallback UI such as a loading state when fetching data.
In other words, it enables a form of Streaming Rendering where parts of the UI are rendered quickly while waiting for the data to be fully prepared, and the rest is displayed once the data is ready.

Suspense, with these common features, can be used as follows:

🟢 Nuxt4

<script setup lang='ts'>
const { data, pending } = await useAsyncData('lists', () =>
  $fetch('/api/lists')
)
</script>

<template>
  <Suspense>
    <template #default>
      <ul>
        <li v-for="list in data" :key="list.id">
          {{ list.title }}
        </li>
      </ul>
    </template>
    <template #fallback>
      <p>Data is being fetched!</p>
    </template>
  </Suspense>
</template>

🔵 Next.js

import { Suspense } from 'react'
import Lists from './Lists'

export default function Page() {
  return (
    <div>
      <Suspense fallback={<p>Data is being fetched!</p>}>
        <Lists />
      </Suspense>
    </div>
  )
}

2. useRouter()

As expected, both frameworks provide page-based route functionality,
so useRouter() has the same function of manipulating route history.
Like the rendering mode, this API also provides additional features only in Nuxt4.

🟢 Nuxt4

const router = useRouter()
const route = useRoute()

// Similar functionality to Next
router.push({ path: '/main' })    // Navigate to the page | We recommend using NavigateTo()
router.back()                     // Navigate to the previous page
router.forward()                  // Navigate to the next page
router.replace('/user')           // Route History 교체 | We recommend using NavigateTo()

route.fullpath            // The URL associated with the current route, including path, query, and hash
route.path                // The encoded route name section of the URL
route.hash                // The decoded hash section of the URL starting with #
route.query               // Query parameters
route.match               // The normalized route array that matches the current route location
route.meta                // Metadata attached to a record
route.name                // The unique name of a route record
route.redirectedFrom      // The route location attempted before reaching the current route location (useful)

// Only Nuxt features
router.go(-1)                     // Same as router.back()
router.go(3)                      // Navigate to the third page from the Route History Stack
router.addRoute({ name: 'home', path: '/home', component: Home }) // Add a new Route History
router.removeRoute('home')  // Remove Route History
router.getRoutes()          // Get all Route History list
router.hasRoute('home')     // Check if the route name is present in a specific Route History
router.resolve({ name: 'home' })   // Pass the normalized route location

// The navigation guard is also supported, but it's better to use Route Middleware!

🔵 Next.js

In Next.js, you can only use it by defining it as a client component, that is, use client.

import { usePathname, useSearchParams } from 'next/navigation'

const router = useRouter()
const path = usePathname()
const searchParams = useSearchParams()

// Similar functionality to Nuxt
router.push('/main', { scroll: true })    // Navigate to the page
router.back()                     // Navigate to the previous page
router.forward()                  // Navigate to the next page
router.replace()                  // Replace Route History

path                            // The current URL path
searchParams                    // Query parameters

// Only Next features
router.prefetch()                 // Prefetch Route

As you can see, Nuxt4 supports standard built-in functions better than Next! This is because you can use it more flexibly in various conditions, making it more convenient and efficient to create an environment.

3. useState()

Suprise? Nuxt4 also provides basic built-in state functions.
It’s the same name as Next! However, in the Nuxt ecosystem, pinia is generally used for state management.

First, let’s see how to use the state management function that is provided by default.

🟢 Nuxt4

<script setup lang="ts">
// useState (Nuxt's built-in state management)
const counter = useState('counter', () => 0)
const user = useState('user', () => null)

// Auto synchronization between server and client
// Pinia integration is also smooth
</script>

🔵 Next.js

// React's built-in state management is used
const [counter, setCounter] = useState(0)

// Global state is managed using Context API, Zustand, Redux, etc.
// Server state needs to be managed separately

Nuxt4 provides SSR-friendly built-in state functions, but Next relies on state management libraries in the react ecosystem, which is the difference.

4. Fetch() / useFetch()

The APIs I’ll introduce next are similar in function, but slightly different in name.
First, the data lookup function can be used as follows:

🟢 Nuxt4

<script setup lang="ts">
  // Built-in automatic type inference, caching, error handling
  const { data, pending, error, refresh } = await useFetch('/api/users')

  // Rich options
  const { data } = await useFetch('/api/posts', {
    key: 'posts',
    server: true,
    lazy: true,
    transform: (data) => data.map(post => ({ ...post, formatted: true })),
    onRequest({ request, options }) {
      // Processing before request
    },
    onResponse({ response }) {
      // Processing after response
    }
  })
</script>

🔵 Next.js

  const data = await fetch('/api/users', {
    cache: 'force-cache', // 'no-store', 'reload', etc.
    next: {
      revalidate: 60,
      tags: ['users']
    }
  })

Here too, Nuxt4 provides richer functionality.
Therefore, Next.js is generally composed of an internal fetch() function and an external library.

5. Image Optimize

In the frontend, it is essential to perform image optimization.
Both frameworks provide components that can perform basic image optimization. Let’s take a look together!

🟢 Nuxt4

<template>
 // NuxtImg, NuxtPicture components
  <NuxtImg
    src="/hero.jpg"
    width="800"
    height="600"
    format="webp"
    quality="80"
    loading="lazy"
    placeholder="blur"
    sizes="sm:100vw md:50vw lg:400px"
  />
</template>
<script setup lang="ts">
// Programming way
const img = useImage()
const optimizedSrc = img('/hero.jpg', { width: 400, quality: 80 })
</script>

🔵 Next.js

import Image from 'next/image'

<Image
  src="/hero.jpg"
  width={800}
  height={600}
  alt="Hero image"
  quality={80}
  loading="lazy"
  placeholder="blur"
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

6.useCookie(), cookies

It’s obvious, but the cookie management saved on the browser is also similar to each other.
Let’s take a look together!

🟢 Nuxt4

<script setup lang="ts">
// useCookie
  const counter = useCookie('counter', {
    default: () => 0,
    serializer: {
      read: (value: string) => {
        return value ? parseInt(value) : 0
      },
      write: (value: number) => {
        return value.toString()
      }
    },
    maxAge: 60 * 60 * 24 * 7 // 1 week
  })

  // Automatic synchronization by reactivity
  counter.value++ // Automatically saved in the cookie
</script>

🔵 Next.js

// cookies() function (App Router)
import { cookies } from 'next/headers'

const cookieStore = cookies()
const theme = cookieStore.get('theme')
cookieStore.set('theme', 'dark')

// In the client, you can use document.cookie directly or use a library like js-cookie
import Cookies from 'js-cookie'
Cookies.set('theme', 'dark', { expires: 7 })

7. SEO / Head

The metadata management is also provided similarly by both frameworks.

🟢 Nuxt4

<script setup lang="ts">
 // useHead - Responsive metadata
  useHead({
    title: computed(() => `${post.value?.title} - My Blog`),
    meta: [
      { name: 'description', content: () => post.value?.excerpt },
      { property: 'og:image', content: () => post.value?.image }
    ],
    link: [
      { rel: 'canonical', href: () => `https://example.com${route.path}` }
    ]
  })

  // useSeoMeta - SEO-specific
  useSeoMeta({
    title: 'My Amazing Site',
    ogTitle: 'My Amazing Site',
    description: 'This is my amazing site, let me tell you all about it.',
    ogDescription: 'This is my amazing site, let me tell you all about it.',
    ogImage: 'https://example.com/image.png',
    twitterCard: 'summary_large_image',
  })
</script>

🔵 Next.js

  // Metadata API (App Router)
  export const metadata: Metadata = {
    title: {
      template: '%s | My Blog',
      default: 'My Blog'
    },
    description: 'This is my blog',
    openGraph: {
      title: 'My Blog',
      description: 'This is my blog',
      images: ['https://example.com/og.jpg'],
    },
    twitter: {
      card: 'summary_large_image',
    },
  }

  // Dynamic metadata
  export async function generateMetadata({ params }): Promise<Metadata> {
    const post = await getPost(params.id)
    return {
      title: post.title,
      description: post.excerpt,
    }
  }

In Nuxt4, you can easily set Head on each page as a composable function,
and Next uses static export methods, making it a feature.

Conclusion

As you can see, we compared and examined the APIs provided by Nuxt4 and Next today.

It’s clear that there are differences in how each framework uses the built-in functions.
Nuxt4 provides basic functions that are more usefully used.

It seems that the future direction of frontend technology is somewhat clear.
I think the functions provided by each framework are similar (or almost identical) to each other.
This is because developers who use these tools are ultimately humans, and the functions they think are inconvenient or necessary are almost the same.
(Aliens don’t use them!)

Next, I’ll explore the performance comparison between Nuxt4 and Next! See you next time!


Reference

Dewdew of the Internet © 2024