Nuxt
Nuxt adds file-based routing, server routes, auto-imports and a build target per rendering mode on top of Vue.
app/pages/products/[id].vue -> /products/:idServer rendering by hand means two builds, a Node server, a router configured twice and a way to pass data from server to browser. Nuxt is the framework that owns that wiring. This course documents Nuxt 4, which builds on Vue 3.5 and Vue Router 5.
What it gives you
File-based routing. Every file under app/pages/ becomes a route. app/pages/about.vue is /about, index.vue is the folder's root, [id].vue is a dynamic segment, [[slug]].vue is an optional one and [...slug].vue catches everything below it. You still render pages through a <NuxtPage> component, and definePageMeta attaches route metadata such as name, layout or middleware from inside the page.
Server routes. Files under server/api/ export a handler built with defineEventHandler and are served under /api. A method suffix narrows them: server/api/items.get.ts answers only GET /api/items.
Auto-imports. Vue APIs such as ref and computed, Nuxt composables such as useRoute, and your own files in app/components/, app/composables/ and app/utils/ are available without an import line. The longhand still works and is worth knowing: import { ref, computed } from '#imports' is exactly what the auto-import resolves to, and imports.autoImport: false in nuxt.config.ts turns the shorthand off entirely.
One build per mode. ssr: false builds a SPA, nuxt generate prerenders, and routeRules mixes modes per path in a single deployment.
Example
<!-- app/pages/products/[id].vue -->
<script setup lang="ts">
definePageMeta({
name: 'product-detail',
})
const route = useRoute()
const id = computed(() => String(route.params.id))
</script>
<template>
<section>
<h1>Product {{ id }}</h1>
<p>Route name: {{ route.name }}</p>
<nuxt-link to="/products">All products</nuxt-link>
</section>
</template>
<style scoped>
section {
display: grid;
gap: 0.5rem;
}
</style>When to use it
- Adding server rendering to an app without hand-writing two Vite builds and a Node server.
- Keeping an API token out of the browser bundle by moving the call into server/api.
- Serving a prerendered marketing site and a client-rendered admin area from one deployment.
More examples
A server route beside the page
`server/` code is never bundled into the browser, so this is the right place for a database client or an API key. `getRouterParam`, `createError` and the rest of the request helpers are auto-imported here the same way Vue APIs are in components. The `.get.ts` suffix means a POST to the same path returns 405 instead of running this handler.
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({ statusCode: 400, statusMessage: 'Missing id' })
}
return { id, name: `Product ${id}`, inStock: true }
})
// GET /api/products/7
// Output: { "id": "7", "name": "Product 7", "inStock": true }Choosing a mode per route
One build, three behaviours. The home page becomes a static file at build time. Product pages are rendered on the server and cached for an hour, with the next request after expiry served stale while a fresh copy is built. Everything under `/admin` ships as a client-rendered shell, which is what you want for pages that are private and never crawled.
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
routeRules: {
'/': { prerender: true },
'/products/**': { swr: 3600 },
'/admin/**': { ssr: false },
},
})Turning the shorthand off
`scan: false` keeps the framework's own auto-imports such as `ref` but stops scanning your `app/composables/` and `app/utils/` folders, so your helpers need real import lines. Teams that dislike identifiers appearing from nowhere use this, or `autoImport: false` to require imports for everything. Either way the code is identical to the auto-imported version; only the import lines differ.
// nuxt.config.ts
export default defineNuxtConfig({
imports: {
scan: false,
},
})
Discussion