These six chapters are the core of "using Nuxt": the Building Nuxt apps section explains them; this page pins what the docs literally say, including option lists and defaults you can be quizzed on.
<NuxtLink> for navigation; useRoute() for params (route.params.id); route middleware in three kinds (anonymous, named, global); route validation with definePageMeta({ validate }) returning true, false (→ 404) or an error object ({ status, statusText }).app.head: charset is utf-8 and viewport is width=device-width, initial-scale=1 unless overridden; app.head cannot be reactive, use useHead in app.vue for that.useHead takes title, titleTemplate, meta, link, script, style, noscript, htmlAttrs, bodyAttrs; values may be refs, computeds or getters. useSeoMeta is the flat, typed form (100+ keys such as ogTitle, twitterCard) that avoids the name/property mistake. useHeadSafe for untrusted input.<Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html>, <Head> (capitalised to avoid clashing with native tags); <Head> groups them.%s or a function; template params (%siteName, %separator) set once via templateParams; body tags with tagPosition: 'bodyClose'; a pattern of setting title in definePageMeta and reading route.meta.title in a layout's useHead; external CSS via link entries.app.pageTransition: { name: 'page', mode: 'out-in' } plus .page-enter-active etc. in global CSS; layout transitions with app.layoutTransition; both overridable per page in definePageMeta and disabled with false. Pages and layouts need a single root element.pageTransition: { onBeforeEnter, onEnter, onAfterEnter } in definePageMeta; dynamic transitions by setting to.meta.pageTransition.name in middleware; <NuxtPage :transition> as a global override.experimental.viewTransition: true | 'always' | false; true skips users with prefers-reduced-motion: reduce; app.viewTransition: false disables globally with per-page opt-in via definePageMeta({ viewTransition }); view transition types (4.4) let a page pass types to the browser so CSS can target them.<Suspense> lets pages await data, and the payload prevents double fetching; a bare await $fetch in setup fetches twice. $fetch (ofetch) for handlers and server code; type a value that holds $fetch with typeof $fetch; pass client headers with useRequestHeaders(['cookie']) or useRequestFetch.data, status ('idle' | 'pending' | 'success' | 'error'), pending (kept aligned with status), error, refresh/execute, clear.lazy, server, immediate, default, transform, pick, watch, deep, dedupe ('cancel' | 'defer'), getCachedData, enabled (MaybeRefOrGetter<boolean>), serialize, plus key for useAsyncData. useFetch adds every ofetch option (method, query, body, headers, baseURL, interceptors).refresh/execute re-run; clear() resets and cancels; watch adds sources; a computed URL (getter) refetches on change; immediate: false waits for execute().appendResponseHeader(event, 'set-cookie', ...).defineNuxtComponent with asyncData() and fetchKey. Serialisation: the payload uses devalue (Dates, Maps, Sets, refs survive); data returned from API routes goes through JSON.stringify unless you define toJSON() or use the serialize option / a library such as superjson.fetch and a ReadableStream; parallel requests with Promise.all inside one useAsyncData handler.useState(key, init) for SSR-friendly shared state; best practice: never define state with ref at module level (shared between requests, memory leaks). Initialising once with callOnce; shared state through a composable that returns useState('key', ...); shallowRef for large non-reactive data; Pinia via @pinia/nuxt for stores; other libraries are fine as long as they create state per request.app:error), Nitro lifecycle, JS chunk loading. Vue errors through onErrorCaptured, the vue:error hook or vueApp.config.errorHandler; startup errors fire app:error; Nitro errors cannot be handled in the app but render the error page; chunk errors are handled by experimental.emitRouteChunkError ('automatic' reloads on the next navigation, 'automatic-immediate' immediately, 'manual' fires the hook only, false disables).error.vue next to app.vue, receives error: NuxtError with status, statusText, message, data, cause, fatal; rendering it is a separate page load so middleware runs again; clearError({ redirect }) leaves it; in development the cause chain is shown.useError(), createError() (string or object; server-side throw → error page, client-side → non-fatal unless fatal: true), showError() (calls app:error), clearError(), <NuxtErrorBoundary>.export default defineNuxtConfig({
app: {
head: { titleTemplate: '%s %separator %siteName', templateParams: { siteName: 'Shop', separator: '·' } },
pageTransition: { name: 'page', mode: 'out-in' },
layoutTransition: { name: 'layout', mode: 'out-in' },
},
experimental: { viewTransition: true, emitRouteChunkError: 'automatic' },
})
<script setup lang="ts">
definePageMeta({
validate: route => /^[a-z0-9-]+$/.test(String(route.params.slug)) || { status: 404, statusText: 'Article not found' },
pageTransition: { name: 'slide', mode: 'out-in' },
})
const route = useRoute()
const enabled = computed(() => !!route.params.slug)
const { data: article, status, error, refresh } = await useFetch(() => `/api/articles/${route.params.slug}`, {
key: () => `article:${route.params.slug}`,
pick: ['title', 'body', 'publishedAt'],
dedupe: 'defer',
enabled, // 4.5: skip the request while false
})
if (error.value) throw createError({ status: error.value.status ?? 500, statusText: 'Could not load article', fatal: true })
useSeoMeta({ title: () => article.value?.title, description: () => article.value?.body.slice(0, 150) })
</script>
<script setup lang="ts">
import type { NuxtError } from '#app'
defineProps<{ error: NuxtError }>()
</script>
<template>
<main>
<h1>{{ error.status }}</h1>
<p>{{ error.statusText }}</p>
<button type="button" @click="clearError({ redirect: '/' })">Home</button>
</main>
</template>
The 4.x docs and Nuxt 4.5's NuxtError type use status and statusText; statusCode and statusMessage still work but are marked deprecated. Older blog posts (and the h3 v1 examples in this app's module-author sections) use the old names; in the room, use the current ones and mention the aliases.
templateParams and a titleTemplate with %siteName, then override the title in one page and view the rendered <title>.validate that returns an error object and check the HTTP status with curl -I.experimental.emitRouteChunkError between 'automatic' and 'manual', deploy a new build with a tab open, and navigate."key for useAsyncData, then lazy, server, immediate, default, transform, pick, watch, deep, dedupe with cancel or defer, getCachedData, enabled as a reactive boolean since 4.5, and serialize; useFetch additionally takes every ofetch option such as method, query, body, headers, baseURL and the interceptors. They return data, status, pending, error, refresh or execute, and clear. The rule the docs stress is that components sharing a key must use consistent options, because the entry is shared."
Nothing yet. Notes filed here show up on this page and the section overview.
Getting started I — installation, configuration, views, assets, styling
The first Getting Started chapters — creating a project, nuxt.config with environment overrides, runtimeConfig versus app.config, external config files, app.vue/pages/layouts, the public and assets directories, and every way to load styles.
Getting started III — server, layers, prerendering, deployment, testing, upgrade
The last Getting Started chapters — the Nitro server, layers and their priority, crawl-based prerendering and payload extraction, deployment targets and presets, the @nuxt/test-utils testing setup, and the upgrade guide's list of Nuxt 4 changes.