What is Nuxt?
Nuxt is the Vue meta-framework. Vue gives you components and reactivity; Nuxt adds the parts every real application needs — routing, server rendering, data fetching, a server runtime and a module ecosystem — and wires them together with sensible defaults.
It is the Vue counterpart to Next.js, and it follows the same broad philosophy: use the file system for routing, render on the server by default, and make data fetching a first-class concern. If you know Vue, Nuxt feels like a natural next step rather than a new framework.
File-based routing
Files in pages/ become routes. Dynamic segments use square brackets, and nested folders create nested routes.
pages/
├── index.vue # /
├── about.vue # /about
├── blog/
│ ├── index.vue # /blog
│ └── [slug].vue # /blog/:slug
└── dashboard/
├── index.vue # /dashboard
└── settings.vue # /dashboard/settings
<!-- pages/blog/[slug].vue -->
<script setup>
const route = useRoute();
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`);
</script>
<template>
<article>
<h1>{{ post.title }}</h1>
<div v-html="post.body" />
</article>
</template>
useRoute gives you the current route, and NuxtLink handles client-side navigation with prefetching. Layouts in layouts/ wrap pages and let you share a shell across many routes.
Auto-imports
Nuxt automatically imports Vue’s APIs, its own composables, and your components and composables. You write less boilerplate and files stay focused.
<!-- components/UserCard.vue -->
<script setup>
const props = defineProps({ user: Object });
</script>
<template>
<article class="card">
<h3>{{ user.name }}</h3>
</article>
</template>
Components in components/ are available anywhere without importing them, and your own composables in composables/ are auto-imported too. The result is less ceremony and fewer import lists to maintain.
Data fetching with useFetch
useFetch and useAsyncData are the recommended way to load data. They run during server rendering, transfer the result to the client and expose loading and error state.
<!-- pages/posts.vue -->
<script setup>
const { data: posts, pending, error } = await useFetch("/api/posts");
</script>
<template>
<p v-if="pending">Loading…</p>
<p v-else-if="error">Could not load posts.</p>
<PostList v-else :posts="posts" />
</template>
Because the request happens on the server, the first paint already includes the data. That avoids the flash of empty content you get from fetching in onMounted, and it works with caching, revalidation and custom keys for finer control.
The Nitro server
Nuxt includes a server engine called Nitro. Files in server/api/ become API endpoints, and server/routes/ can serve custom handlers.
// server/api/users.get.ts
export default defineEventHandler(async () => {
const config = useRuntimeConfig();
return $fetch("https://api.example.com/users", {
headers: { Authorization: config.apiKey },
});
});
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const user = await db.user.create({ data: body });
return { user };
});
This keeps secrets and third-party credentials on the server. The client calls your own endpoint, and Nitro handles the rest. The same engine deploys to Node, serverless functions, edge runtimes and static hosts through adapters.
SEO, meta and modules
Nuxt makes server-friendly SEO straightforward with composables.
<!-- pages/about.vue -->
<script setup>
useSeoMeta({
title: "About us",
description: "Who we are and what we build.",
ogImage: "/og/about.png",
});
</script>
The useHead and useSeoMeta composables manage titles, descriptions, canonical URLs and social tags, and they render correctly on the server so crawlers see them. Beyond that, the Nuxt module ecosystem adds authentication, content, images, analytics and more with a line or two of config.
Rendering modes
Nuxt supports several rendering strategies and lets you choose per route:
- Universal (SSR) renders on the server and hydrates on the client — the default.
- Static (SSG) prerenders pages at build time.
- SPA renders entirely on the client, like a classic Vue app.
- Hybrid mixes them, so marketing pages can be static while a dashboard is client-rendered.
You can also enable incremental static regeneration and route rules to fine-tune caching and rendering without restructuring the app.
Best practices
- Use
useFetchanduseAsyncDatainstead of fetching inonMounted. - Keep secrets and third-party calls in
server/apiroutes. - Let auto-imports do their job; do not fight them with manual imports.
- Define SEO metadata with
useSeoMetaon every page. - Choose a rendering mode per route rather than one mode for everything.
- Use
NuxtLinkfor internal navigation so prefetching and transitions work. - Organise reusable logic into composables in
composables/.
Common mistakes
- Fetching data in
onMountedand losing the SSR benefit. - Exposing API keys by calling third parties directly from the client.
- Reaching for a global store when
useStateor a composable would do. - Ignoring route rules and over-rendering pages that could be static.
- Assuming Nuxt 2 module APIs work unchanged in Nuxt 3.
- Skipping SEO metadata because the framework makes it easy to forget.
Where to go next
Nuxt turns Vue into a full-stack platform. Deepen your Vue knowledge, add TypeScript, and compare the approach with Next.js and SvelteKit. Then build a small app with a page, a server route and useFetch to feel how naturally the pieces fit.