Nuxt docs, chapter by chapter

Recipes, best practices and AI docs

The Guide's Recipes (Custom Routing, Custom useFetch, Mostly-Static Sites, Sessions and Authentication, Vite Plugins), the Best Practices pages (Accessibility, Dev Containers, Hydration, Performance, Plugins) and the AI pages (llms.txt, MCP server).

The recipes are the docs' worked examples for things every real app needs, and the best-practices pages are the docs' own checklists. Interviewers like them because they have a canonical answer: "how would you add auth to a Nuxt app" has a documented recipe, and answering with it (then improving on it) is the ideal shape.

Know — Recipes

  • Custom Routing: add routes with router.options.ts (routes(defaultRoutes) returning a new array), with the pages:extend hook in nuxt.config (push, filter or rename route records), or from a module with extendPages. Router options: router.options.ts (full Vue Router options, functions allowed) or router.options in nuxt.config (JSON-serialisable only: hashMode, scrollBehaviorType: 'smooth', linkActiveClass, strict, sensitive). Hash mode only makes sense with ssr: false; smooth scrolling to hash links via scrollBehaviorType.
  • Custom useFetch: a plugin creates $fetch.create({ baseURL, onRequest (adds the Authorizationheader from the session), onResponseError (401 →navigateTo('/login')) }) and provides it as $api; a useAPI composable wraps useFetch(url, { ...options, $fetch: useNuxtApp().$api }). The newer macro form is export const useAPI = createUseFetch({ baseURL }) in composables/.
  • Mostly-Static Sites: routeRules: { '/**': { prerender: true, noScripts: true } } prerenders everything and ships no JavaScript; NuxtLink then renders plain anchors (full page loads between script-free routes); islands (.server.vue) for server-rendered widgets; lazy hydration for the few interactive parts on routes that keep scripts.
  • Sessions and Authentication: npx nuxt module add auth-utils; set NUXT_SESSION_PASSWORD (32+ characters; generated in dev); a login route server/api/login.post.ts validates the body (readValidatedBody with zod) and calls setUserSession(event, { user }); the login page uses useUserSession() (loggedIn, user, fetch, clear); protect API routes with requireUserSession(event) (401) and app routes with middleware redirecting when !loggedIn.value. Sessions are sealed, encrypted cookies.
  • Vite plugins: add to vite.plugins in nuxt.config; in a module use addVitePlugin.

Know — Best practices

  • Accessibility: route announcements with <NuxtRouteAnnouncer> (and <NuxtAnnouncer> for in-page changes), a page title per page (useHead/useSeoMeta), semantic links with <NuxtLink>, focus management on navigation, and scroll behaviour that respects users; resources include the WAI-ARIA authoring practices and the a11y module ecosystem.
  • Dev Containers: .devcontainer/devcontainer.json with a Node image, forwarded port 3000 and a postCreateCommand installing dependencies; open via the VS Code prompt, the command palette, or the devcontainer CLI.
  • Hydration: fix mismatches because they cost a re-render (performance) and break interactivity (functionality); detect them via development console warnings; common causes: browser-only APIs in the server context, inconsistent data (random values, non-deterministic sorting), conditional rendering based on client state, third-party libraries with side effects, and time-dependent content. The hydration section goes deeper.
  • Performance: use the built-ins first, NuxtLink prefetching, hybrid rendering, Lazy components, lazy hydration, data fetching with pick/transform; the core modules @nuxt/image, @nuxt/fonts, @nuxt/scripts; profile with nuxt analyze, Nuxt DevTools and Chrome DevTools.
  • Plugins: avoid costly setup (a plugin runs on every server request and every client load), prefer composables to plugins when logic is only needed in some components, and mark async plugins parallel so they do not block the rest.

Know — AI docs

  • llms.txt: https://nuxt.com/llms.txt (index) and https://nuxt.com/llms-full.txt (everything), plus .md on any docs URL or Accept: text/markdown; add them to Cursor, Windsurf or any tool that accepts documentation URLs.
  • MCP server: https://nuxt.com/mcp (streamable HTTP) with tools for documentation search, blog, deployment guides, modules and changelog, resources and prompts; setup snippets for ChatGPT, Claude Code, Claude Desktop and Cursor; tools can be limited per client.

How it works

The auth recipe end to end, compressed:

server/api/login.post.ts
import { z } from 'zod'

const body = z.object({ email: z.string().email(), password: z.string().min(8) })

export default defineEventHandler(async (event) => {
  const { email, password } = await readValidatedBody(event, body.parse)
  const user = await verifyCredentials(email, password) // your own lookup + verifyPassword
  if (!user) throw createError({ status: 401, statusText: 'Bad credentials' })
  await setUserSession(event, { user: { id: user.id, name: user.name } })
  return {}
})
server/api/account.get.ts
export default defineEventHandler(async (event) => {
  const { user } = await requireUserSession(event) // 401 when missing
  return useDb().accounts.forUser(user.id)
})
app/middleware/authenticated.ts
export default defineNuxtRouteMiddleware(() => {
  const { loggedIn } = useUserSession()
  if (!loggedIn.value) return navigateTo('/login')
})

The custom fetch recipe in its macro form, and the mostly-static configuration:

app/composables/useAPI.ts
export const useAPI = createUseFetch({
  baseURL: '/api',
  onResponseError({ response }) {
    if (response.status === 401) navigateTo('/login')
  },
})
nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/**': { prerender: true, noScripts: true }, // static HTML, zero JS
    '/app/**': { prerender: false, noScripts: false }, // the interactive part keeps its scripts
  },
})
Gotcha· noScripts and NuxtLink

On a route rendered with noScripts, there is no router in the browser, so NuxtLink renders a plain anchor and navigation is a full page load. Mixing script-free and interactive routes therefore costs a reload at the boundary, which the docs call out as the trade-off to accept deliberately.

Verify before the interview:

The current nuxt-auth-utils API names in the recipe (setUserSession, requireUserSession, useUserSession) and the session password requirement.

docs ↗

Exercise

Exercise
  • Implement the sessions recipe in a playground, then call the protected API route without a cookie with curl and confirm the 401.
  • Convert the plugin-based $api recipe to createUseFetch and compare the generated types of both composables.
  • Configure a mostly-static site with one interactive route; view source on both kinds of route and count <script> tags.

Be able to say

Be able to say· How would you add authentication to a Nuxt app?

"I would start from the documented recipe with nuxt-auth-utils: a sealed, encrypted cookie session keyed by NUXT_SESSION_PASSWORD, a login API route that validates the body and calls setUserSession, useUserSession on the client for loggedIn, user and clear, requireUserSession inside every protected server route so the API is the real gate, and a route middleware that redirects anonymous users for the user experience. From there I would add password hashing with the module's helpers or an OAuth provider, forward cookies during SSR with useRequestFetch, and make sure no personalised route is cached with swr."

Your notes

Open in Notes

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