Building Nuxt apps

Building Nuxt apps

The application developer's map of Nuxt 4 — directory conventions, the request lifecycle, and how routing, data fetching, state, plugins, errors, SEO and rendering strategies fit together.

The existing internals section explains Nuxt from a tooling author's point of view: hooks, kit, the build. This section is the other half, the one most Nuxt interviews actually test: how you build applications with it. Where does code go, how does a request flow, how do you fetch data without fetching it twice, where does state live, how do errors surface, how do you decide what to render where.

The snapshot is Nuxt 4.5 on Vue 3.5, Nitro 2 and Vite 8. Nuxt 5 is expected in the last quarter of 2026 and the landscape section tracks what it changes; the pages here note the differences with a Nuxt 5 badge where they matter.

Know

  • Directory conventions carry meaning. app/ is the Vue application (pages/, layouts/, components/, composables/, utils/, middleware/, plugins/, assets/, app.vue, error.vue, app.config.ts); server/ is Nitro (api/, routes/, middleware/, plugins/, utils/); shared/ is code both sides may import; public/ is served as-is; content/, layers/, modules/ are what their names say. Nuxt 4 moved the app code into app/ so the server and client trees are visibly separate and so that file watchers and IDEs do not index the server code as part of the Vue app.
  • One request, two renders. The server runs Nitro middleware, then route rules, then renders the Vue app for the URL: plugins, route middleware, layout, page, data fetching inside <Suspense>. The HTML ships with a serialised payload (#__NUXT_DATA__). The client boots the same app, hydrates against that HTML, and reuses the payload instead of refetching. Every later navigation is client-side.
  • Auto-imports are the ergonomic layer. Components, composables, utils and Nuxt/Vue APIs are available without imports; #imports gives you the explicit form, imports.autoImport: false turns it off. Knowing where the scanner looks (top level of composables/ and utils/, recursive for components/) explains the "not defined" bugs.
  • Configuration has three homes. nuxt.config.ts for build and framework options, runtimeConfig for values that change per environment (overridable with NUXT_* variables at runtime, private by default, public for the client), app.config.ts for reactive public configuration such as theme tokens.
  • Rendering is per route. The default is universal rendering; routeRules switch individual routes to prerendered, cached (swr), incrementally regenerated (isr), client-only or redirected. <ClientOnly>, .client.vue, server components and lazy hydration refine what ships to the browser.
  • The two mistakes that identify a junior Nuxt developer: calling $fetch directly in setup (it runs on the server and again on the client, and nothing lands in the payload) and putting per-user state in a module-level variable (shared between requests on the server).

How the pages fit

PageWhat it prepares you for
Routing and pagesFile-based routes, definePageMeta, layouts, middleware, NuxtLink, typed routes, route rules
Data fetchinguseFetch vs useAsyncData vs $fetch, the payload, keys, caching, headers, interceptors
State and runtime configuseState, runtimeConfig, app.config, useCookie, what is safe to serialise
Plugins and auto-importsPlugin order and provide, app hooks, callOnce, component and composable scanning
Error handlingcreateError, error.vue, NuxtErrorBoundary, fatal vs non-fatal, server route errors
SEO, head and mediauseSeoMeta, Unhead, OG images and sitemaps, @nuxt/image, @nuxt/fonts, @nuxt/scripts
Rendering strategiesSSR, SSG, ISR/SWR, SPA per route, islands, lazy hydration, streaming, and how to choose
Internationalisation@nuxtjs/i18n strategies, lazy messages, SEO tags, formatting, RTL

How it works

The request lifecycle as a sequence, which is worth being able to narrate:

Browser GET /products/42
  → Nitro: server/middleware/*  (auth headers, request id)
  → routeRules for /products/**  (swr? prerender? ssr:false?)
  → Vue app created for this request
      plugins (server) in order → app:created
      router resolves /products/[id]
      route middleware (global, then page's)  → may redirect/abort
      layout + page setup inside <Suspense>
        useAsyncData/useFetch run on the server, results → payload
      render to HTML, head tags from Unhead
  ← HTML + #__NUXT_DATA__ payload + preload hints
Browser
  → entry chunk, plugins (client), app:created
  → hydrate against existing DOM (payload reused, no refetch)  → app:mounted
  → later: <NuxtLink> navigation runs middleware + page setup client-side
Gotcha· Reading the docs for the wrong major

The 3.x and 4.x docs live at different paths (/docs/3.x/... vs /docs/4.x/...) and defaults differ (data is undefined not null, useAsyncData is shallow, app/ is the source directory). When a Stack Overflow answer disagrees with what you see, check which major it is talking about before "fixing" your code.

Exercise

Exercise
  • Create a fresh npm create nuxt@latest app and add one file to each directory listed above until you can explain, without looking, what each directory contributes to the request lifecycle.
  • Add console.log calls in a server plugin, a client plugin, a global middleware, a layout and a page; load the page directly and then navigate to it from another page. Write down the two orders.

Be able to say

Be able to say· Walk me through what happens when a user requests a page from a Nuxt app.

"Nitro receives the request and runs server middleware and route rules first; a route rule might serve a prerendered or cached page and skip rendering altogether. Otherwise Nuxt creates a fresh Vue app for the request, runs the server plugins, resolves the route, runs global and page middleware, and renders the layout and page inside Suspense so useAsyncData calls can complete on the server. The results are serialised into the payload, the head tags are generated by Unhead, and the HTML goes out. In the browser the same app boots, runs the client plugins, and hydrates against the existing DOM using the payload, so nothing is fetched twice. From then on navigation is client-side: middleware and page setup run in the browser and data is fetched from the API."

Pages in this section

  1. 01Routing and pagesFile-based routing in Nuxt 4, definePageMeta, layouts, route middleware on server and client, NuxtLink prefetching, typed routes and the route rules that live in Nitro.
  2. 02Data fetchinguseFetch, useAsyncData and $fetch, the payload and why data is not fetched twice, keys, options, caching with getCachedData, forwarding headers, interceptors and error handling.
  3. 03State and runtime configuseState for SSR-safe shared state, what the payload can serialise, runtimeConfig versus app.config versus environment variables, useCookie, and when a store is the right tool.
  4. 04Plugins and auto-importsPlugin order, dependsOn and parallel, providing typed helpers, app hooks, callOnce and onNuxtReady, and how components, composables and utils are scanned and named.
  5. 05Error handlingWhere errors happen in a Nuxt app and where they surface — createError and error.vue, fatal versus non-fatal, NuxtErrorBoundary, useAsyncData errors, server route errors, hooks and monitoring.
  6. 06SEO, head and mediauseSeoMeta and useHead on Unhead, titles and OG tags, canonical URLs, sitemaps and structured data with the Nuxt SEO modules, and the image, font and script modules that protect LCP and CLS.
  7. 07Rendering strategiesUniversal rendering, SPA, prerendering, SWR and ISR per route, client-only and server components, islands, lazy hydration and streaming, and how to choose per route rather than per app.
  8. 08InternationalisationThe @nuxtjs/i18n module — routing strategies, lazy-loaded messages, locale detection, SEO tags, formatting with Intl, right-to-left layouts and the hydration trap of a locale that differs between server and client.

Your notes

Open in Notes

Nothing yet. Notes filed here show up on every page of this section.