Suggest an editImprove this articleRefine the answer for “What is Nuxt.js and how does it differ from Vue.js?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Nuxt.js** is a meta-framework built on top of Vue.js that adds SSR, file-based routing, and auto-imports automatically. Vue handles the UI layer; Nuxt wraps it with a server and build pipeline. **Key point:** Vue needs a manual `vue-router` config; Nuxt generates routes from your `pages/` folder structure without any configuration.Shown above the full answer for quick recall.Answer (EN)Image**Nuxt.js** is a meta-framework built on top of Vue.js that adds server-side rendering, file-based routing, and auto-imports automatically, turning a folder structure into a full-stack app without manual configuration. ## Theory ### TL;DR - Vue.js is raw dough: you build the router, configure SSR, wire up imports yourself. Nuxt is the pre-built pan: same Vue under the hood, but it reads your `pages/` folder and does the rest. - Main difference: Vue needs a manual `vue-router` config; Nuxt generates routes from file names. - Nuxt renders on the server by default. Vue renders in the browser. - Decision rule: public site needing SEO → Nuxt. Internal dashboard with no SEO requirements → Vue alone. - Nuxt 3 runs on the Nitro engine, which supports Node.js, Vercel, and Cloudflare Workers without changing your app code. ### Quick example The most visible difference is routing. In Vue, you write a config file. In Nuxt, you create a file in the right folder. **Vue.js (manual routing):** ```js // main.js - you write all of this import { createApp } from 'vue' import { createRouter, createWebHistory } from 'vue-router' import Home from './pages/Home.vue' import About from './pages/About.vue' const routes = [ { path: '/', component: Home }, { path: '/about', component: About } ] const router = createRouter({ history: createWebHistory(), routes }) createApp({}).use(router).mount('#app') ``` **Nuxt.js (file-based routing):** ``` pages/ index.vue → / about.vue → /about products/ [id].vue → /products/:id (dynamic route, auto-handled) ``` No config file. Nuxt scans `pages/` at build time and generates the router. Visit `/about` and you get `about.vue`, server-rendered. ### Key difference Nuxt scans `pages/`, `components/`, and `composables/` using Vite, then generates a Vue Router config and a Nitro server bundle. On a request, Nitro runs your page component on Node.js, prefetches data via `useAsyncData` or `useFetch`, and returns full HTML. Vue then hydrates it on the client without re-fetching, because Nuxt transfers the server payload automatically. Google gets real HTML with your content, not a blank `<div id="app">`. ### When to use - **Marketing or product site** → Nuxt. Search engines need to read your content, so SSR matters. - **Internal dashboard or admin panel** → Vue alone. No SEO needs, simpler deployment, no server costs. - **Static blog or docs** → Nuxt with `nuxt generate`. Pre-renders to HTML, served from CDN, fast by default. - **Headless e-commerce** → Nuxt with server routes. Heavy API usage, but product pages still need indexing. - **Quick prototype** → Nuxt. File-based everything cuts setup time to a few minutes. ### Comparison table | Feature | Vue.js | Nuxt.js (v3) | |---|---|---| | **Rendering** | Client-side only | SSR, SSG, SPA, or hybrid per route | | **Routing** | Manual `vue-router` setup | Auto-generated from `pages/` folder | | **Setup time** | 15-30 min of boilerplate | `npx nuxi init` → ready immediately | | **SEO** | Manual `<meta>` tags or plugins | Built-in `useHead()` + server HTML | | **Data fetching** | `onMounted` + `fetch` | `useFetch` / `useAsyncData` (server-first) | | **Bundle splitting** | Manual configuration | Auto per page | | **Server** | None (you add it yourself) | Nitro (Node.js, Vercel, Cloudflare edge) | | **Auto-imports** | No | Yes - composables, components, utils | | **When to use** | SPAs, libraries, embedded widgets | Public sites, e-commerce, full-stack apps | ### How Nuxt handles a request At build time, Nuxt's Vite plugin reads `pages/` and generates a Vue Router config. It also compiles a Nitro server bundle separately. When a request arrives, Nitro matches the route, runs the page component on Node.js, executes all `useAsyncData` and `useFetch` calls server-side, then serializes the result as HTML. The browser receives a full page. Vue hydrates it without a second data request, because the server payload ships inside the HTML response. ### Common mistakes **1. Fetching data in `onMounted` for SSR pages** The `onMounted` pattern is the mistake I see most often in code reviews from developers moving from Vue SPA to Nuxt. ```vue <script setup> // Wrong: runs only on the client onMounted(async () => { data.value = await $fetch('/api/products') }) </script> ``` The server sends an empty page. Search engines see nothing. Users see a blank flash before content appears. ```vue <script setup> // Correct: runs on the server, data is already in the HTML const { data } = await useAsyncData('products', () => $fetch('/api/products')) </script> ``` **2. Using `window` or `document` directly in components** ```vue <script setup> // Crashes on the server - Node.js has no window object const width = window.innerWidth </script> ``` Nuxt runs your component on Node.js first. `window` does not exist there. ```vue <script setup> // Option 1: VueUse composable (handles SSR automatically) const { width } = useWindowSize() // Option 2: guard with process.client if (process.client) { const width = window.innerWidth } </script> ``` **3. Putting API keys in `<script setup>`** ```vue <script setup> const key = 'sk-secret-123' // This ends up in the client bundle </script> ``` Nuxt packages both server and client JS. That key ships to the browser. Use `useRuntimeConfig()` instead. Private keys go under `runtimeConfig` in `nuxt.config.ts` and stay server-only. Public keys go under `runtimeConfig.public`. **4. Adding `process.client` checks inside global middleware** ```ts // middleware/auth.global.ts export default defineNuxtRouteMiddleware((to) => { // Wrong: skipping the server check leaks protected HTML if (process.client && !useCookie('token').value) { return navigateTo('/login') } }) ``` Without the server check, Nuxt renders the protected page HTML and sends it in the initial response before redirecting. The correct version removes `process.client` entirely, so the server intercepts the request before generating any HTML. ### Real-world usage - **Netflix (Voraz)**: Nuxt 2 for SSR video landing pages, later migrated to Nuxt 3 for edge deployment via Nitro. - **Adobe Portfolio**: Static site generation for designer showcases, served from CDN. - **Decathlon**: E-commerce product catalogs with `useAsyncData` for server-rendered product pages. - **Nuxt Commerce**: Open-source Shopify headless starter using server API routes and SSR. ### Follow-up questions **Q:** What is the difference between `useFetch` and `useAsyncData` in Nuxt? **A:** `useFetch` is shorthand that auto-generates a cache key from the URL and calls `useAsyncData` internally. `useAsyncData` takes a manual key and a custom fetcher function, giving you control over caching and deduplication. Use `useFetch` for simple cases; use `useAsyncData` when you need a custom fetcher or a predictable key. **Q:** What is the difference between SSR and SSG in Nuxt? **A:** SSR renders each page at request time on the server, good for dynamic data like product listings or user dashboards. SSG pre-renders pages at build time and serves static HTML from a CDN, good for blogs or marketing pages where content rarely changes. Run `nuxt generate` to produce a static build. **Q:** How does Nuxt 3's Nitro differ from the Nuxt 2 server? **A:** Nuxt 2 used a custom Express-based server. Nitro in Nuxt 3 is a standalone server engine from the UnJS ecosystem. It compiles to a lightweight bundle that runs on Node.js, Deno, Cloudflare Workers, or Vercel Edge Functions without changing your application code. **Q:** A Vue 3 SPA has poor SEO scores. How would you migrate it to Nuxt 3? **A:** Move pages from `src/views/` to `pages/`, remove the manual `vue-router` config, replace `onMounted` data fetching with `useAsyncData`, add `app.vue` as the root layout, then run `npx nuxi upgrade`. After migration, watch for hydration mismatches. Any browser-only API used without a guard will error on the server. Wrap those spots with `<ClientOnly>` or a `process.client` check. ## Examples ### Server-rendered product page with dynamic routing The file `pages/products/[id].vue` auto-handles any URL matching `/products/:id`. No route config needed. ```vue <!-- pages/products/[id].vue --> <script setup> const route = useRoute() // Runs on the server - product data is in the HTML before the browser loads const { data: product, pending } = await useAsyncData( `product-${route.params.id}`, () => $fetch(`/api/products/${route.params.id}`) ) // Meta tags use server data - Google indexes the real title useHead({ title: product.value?.name ?? 'Product', meta: [{ name: 'description', content: product.value?.description }] }) </script> <template> <div v-if="pending">Loading...</div> <div v-else-if="product"> <h1>{{ product.name }}</h1> <img :src="product.image" :alt="product.name" width="300" /> <p>{{ product.description }}</p> </div> </template> ``` The server sends a page with the product title already in the HTML. Google indexes it. The client hydrates without a second network request because Nuxt transfers the `useAsyncData` payload automatically alongside the HTML. ### Auth middleware with server-side redirect This pattern trips up developers coming from Vue SPAs, where middleware is always client-side. ```ts // middleware/auth.global.ts - runs on every route, on server and client export default defineNuxtRouteMiddleware((to) => { const token = useCookie('token') // No process.client guard here - this is intentional // On the server: unauthenticated users never receive the protected HTML if (!token.value) { return navigateTo('/login') } }) ``` In a Vue SPA, the browser renders the protected page first and then redirects. The HTML was already in memory. With this Nuxt pattern, the server sends a 302 before generating any page HTML. The user never touches the protected content. That is both faster and more secure.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.