Nuxt docs, chapter by chapter

Getting started II — routing, SEO, transitions, data, state, errors

The middle Getting Started chapters — Routing, SEO and Meta, Transitions, Data Fetching, State Management and Error Handling — reduced to the statements, options and defaults the docs make.

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.

Know — Routing

  • Pages become routes; <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 }).

Know — SEO and Meta

  • Defaults in 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.
  • Components: <Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html>, <Head> (capitalised to avoid clashing with native tags); <Head> groups them.
  • Features: reactivity through getters; title template as a string with %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.

Know — Transitions

  • Page transitions with 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.
  • JavaScript hooks via pageTransition: { onBeforeEnter, onEnter, onAfterEnter } in definePageMeta; dynamic transitions by setting to.meta.pageTransition.name in middleware; <NuxtPage :transition> as a global override.
  • View Transitions API (experimental): 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.

Know — Data Fetching

  • Why the composables exist: <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.
  • Return values: data, status ('idle' | 'pending' | 'success' | 'error'), pending (kept aligned with status), error, refresh/execute, clear.
  • Options: 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).
  • Caching and refetching: keys identify the shared entry and components sharing a key must use consistent options; reactive keys (a getter or ref) refetch when they change; refresh/execute re-run; clear() resets and cancels; watch adds sources; a computed URL (getter) refetches on change; immediate: false waits for execute().
  • Headers and cookies: forward incoming headers to the API during SSR, and pass cookies set by internal API calls onto the SSR response with appendResponseHeader(event, 'set-cookie', ...).
  • Options API: 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.
  • Recipes: consuming Server-Sent Events over POST with fetch and a ReadableStream; parallel requests with Promise.all inside one useAsyncData handler.

Know — State Management

  • 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.

Know — Error Handling

  • Sources: Vue rendering (SSR and CSR), startup (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 page 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.
  • Utils: useError(), createError() (string or object; server-side throw → error page, client-side → non-fatal unless fatal: true), showError() (calls app:error), clearError(), <NuxtErrorBoundary>.

How it works

nuxt.config.ts
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' },
})
app/pages/articles/[slug].vue
<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>
app/error.vue
<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>
Gotcha· statusCode is the old name

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.

Exercise

Exercise
  • Set templateParams and a titleTemplate with %siteName, then override the title in one page and view the rendered <title>.
  • Add a validate that returns an error object and check the HTTP status with curl -I.
  • Toggle experimental.emitRouteChunkError between 'automatic' and 'manual', deploy a new build with a tab open, and navigate.

Be able to say

Be able to say· Which options do useFetch and useAsyncData accept?

"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."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.