Here are 19 Nuxt interview questions with model answers, written for senior engineers who build with Nuxt 4 or maintain Nuxt 3 apps. They cover data fetching, hydration, state, rendering modes and route rules, middleware and plugins, Nitro server routes, runtime configuration, SEO, testing and the Nuxt 3 to Nuxt 4 upgrade. Core Vue topics such as reactivity, the Composition API and Pinia are on our Vue.js interview questions page. At Ryz, senior Vue and Nuxt engineers are sourced by recruiters, assessed in structured NTRVSTA AI interviews, and reviewed by recruiters before and after. The AI scores are one input. People make the decisions.
Nuxt 4.0 was released in July 2025 and the 4.x line is current. Nuxt 3 still receives maintenance releases, so many teams run either a Nuxt 3 app, a Nuxt 3 app with compatibilityVersion: 4 turned on, or Nuxt 4. Nuxt 5, built on Nitro v3 and h3 v2, is in development, and from Nuxt 4.2 teams can test many of its changes with compatibilityVersion: 5. Tell the candidate which version you run, and accept answers in either vocabulary.
The strongest signal in a Nuxt interview is how a candidate thinks about the two runtimes: what runs on the server, what runs in the browser, what crosses between them in the payload, and when it runs twice by mistake. Ask them to trace a page load from request to hydrated page. Then have them build a page live.
useFetch, useAsyncData and $fetch?useFetch(url) is the shortcut for calling an API in a page or component. useAsyncData(key, fn) wraps any async function, such as several calls, an SDK or a CMS client. Both run on the server during server rendering, store the result in the payload sent with the HTML, and reuse it during hydration instead of fetching again. $fetch is a plain HTTP client: use it in event handlers, actions after user input and server routes, not for initial data in setup.
What a strong answer shows: They explain the payload, and know that calling $fetch directly in setup fetches once on the server and again in the browser.
<script setup lang="ts">
const route = useRoute();
const sort = ref<"price" | "rating">("price");
const { data: product, error } = await useFetch(
() => `/api/products/${route.params.id}`,
{ pick: ["id", "name", "price", "images"] }
);
const { data: reviews, status } = await useFetch("/api/reviews", {
query: { productId: computed(() => route.params.id), sort },
lazy: true,
default: () => []
});
if (error.value) {
throw createError({ statusCode: 404, statusMessage: "Product not found", fatal: true });
}
</script>
Reactive URLs and query values refetch automatically when they change. pick trims what goes into the payload, lazy lets the page render before the reviews arrive, and status drives the loading UI.
What a strong answer shows: They block rendering only on critical data, keep payloads small, and handle the not-found case with a real 404.
useAsyncData, and what changed in Nuxt 4?The key identifies the data in the payload and in Nuxt's cache. useFetch generates one from the URL and options, and useAsyncData takes one explicitly or generates one from the file and line. In Nuxt 4, every call with the same key shares the same data, error and status refs, so calls with one key must not use conflicting options such as different transform or pick. Keys can also be refs or getters, which refetch and store data separately when the key changes, and data is cleaned up when the last component using a key unmounts.
What a strong answer shows: They have debugged two components overwriting each other's data through a shared key and fixed it with distinct keys.
data is a shallowRef, so mutating a nested property no longer triggers updates unless you pass deep: true. data and error default to undefined instead of null. pending is true only while status is "pending", so with immediate: false it starts as false. getCachedData now runs on every fetch, including refreshes triggered by watchers, and receives the cause of the request.
What a strong answer shows: They know null checks and nested mutations are the usual sources of upgrade bugs, and prefer replacing data over mutating it.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
"/": { prerender: true },
"/blog/**": { swr: 3600 },
"/products/**": { swr: 600 },
"/account/**": { ssr: false },
"/api/**": { cors: true, headers: { "cache-control": "no-store" } }
}
});
By default, Nuxt renders on the server for the first request and then runs as a single-page app. Route rules set rendering per path: prerender builds static HTML at build time, swr caches server-rendered responses and revalidates in the background, isr does the same with CDN caching on platforms that support it, and ssr: false renders in the browser only. Rules can also set headers, redirects and CORS.
What a strong answer shows: They choose a mode per route by how fresh and how personal the content is, and never cache personalized pages in a shared cache.
The HTML from the server differs from what the browser renders during hydration. Common causes are dates and times formatted in different time zones, random values or IDs, reading window or localStorage during render, and invalid HTML nesting that the browser repairs. Fixes: format dates with an explicit time zone, use Vue's useId() for IDs, move browser-only reads into onMounted or wrap them in <ClientOnly>, and use useCookie for state both sides can read.
What a strong answer shows: They find the cause instead of hiding the component behind <ClientOnly>, which costs SEO and layout stability.
Use lazy components with the Lazy prefix so their code loads only when rendered. Since Nuxt 3.16, lazy components also accept delayed hydration props, such as hydrate-on-visible, hydrate-on-idle, hydrate-on-interaction and hydrate-never. Server components and islands render parts of the page on the server only, sending HTML without component JavaScript. Keep payloads small with pick and transform, and optimize images with @nuxt/image.
What a strong answer shows: They measure with nuxi analyze and Lighthouse before and after, and know which parts of the page actually need interactivity.
ref declared at module scope dangerous in a Nuxt app?On the server, modules are loaded once and shared across all requests. A ref at module scope therefore holds one user's data and leaks it into the next user's render. Use useState(key, init) for SSR-safe shared state, which is created per request and serialized into the payload, or Pinia through @pinia/nuxt for larger stores. Only put values that can be serialized into state that crosses to the browser.
What a strong answer shows: They recognize cross-request state pollution as a security bug, not only a correctness bug.
runtimeConfig work, and how is it different from app.config?// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
stripeSecretKey: "", // server only: NUXT_STRIPE_SECRET_KEY
public: {
apiBase: "/api" // exposed to the browser: NUXT_PUBLIC_API_BASE
}
}
});
Keys at the top level of runtimeConfig are available only on the server. Keys under public are sent to the browser. Both can be overridden at runtime by environment variables with the NUXT_ prefix, so one build can run in several environments. app.config.ts holds public settings fixed at build time, such as theme or feature labels, and can't be overridden by environment variables.
What a strong answer shows: They never put secrets under public, and know that only keys declared in the config can be overridden by environment variables.
Application code moves into app/ by default: pages, components, composables, layouts, middleware, plugins and app.vue. server/, public/, layers/ and modules/ stay at the project root, and a new shared/ folder holds code used by both the Vue app and the server, with auto-imports for shared/utils and shared/types. Nuxt also generates separate TypeScript projects for app, server, shared and build-time code, so server-only types don't leak into components.
What a strong answer shows: They know Nuxt detects the old layout and keeps it working, and they move folders in a separate commit from behavior changes.
A layer is a partial Nuxt app, with its own components, composables, pages, server routes and config, that another app extends through extends in nuxt.config.ts. Layers suit a shared design system and auth across several apps, or a base app that brands customize. Files in the extending app override files with the same path in the layer. Modules, by contrast, hook into the build with code and are better for integrations.
What a strong answer shows: They choose between layers, modules and a plain package by what needs to be shared: files and conventions, build behavior, or functions.
Route middleware, in app/middleware, runs in the Vue app before navigating to a page: on the server for the first request and in the browser for later navigations. It can redirect with navigateTo or stop with abortNavigation, and a .global suffix runs it on every route. Server middleware, in server/middleware, runs in Nitro on every request to the server, including API calls, before any route handler.
What a strong answer shows: They know route middleware is for user experience, and that APIs must check authorization in server handlers, because anyone can call them directly.
Add a named or global route middleware that redirects anonymous users to login, so the page experience is right. Then enforce access on the server: every API route under server/api reads the session from a cookie, validates it, and returns 401 or 403 itself. Mark private pages ssr: false or send cache-control: private so no shared cache stores them, and pass cookies through when server rendering calls your own API.
What a strong answer shows: Two layers, with the server as the real control. A candidate who relies on route middleware alone is a risk.
Plugins run once when the app is created, which makes them right for registering Vue plugins, providing helpers with provide, and setting up clients such as analytics or a typed API client. Use the .client or .server suffix for one side only. Plugins run in order before the app renders, so slow async work in a plugin delays every page. Mark independent async plugins with parallel: true, and keep heavy setup out of them.
What a strong answer shows: They know plugin cost lands on every request, and they audit plugins when server response times grow.
// server/api/orders.post.ts
import { z } from "zod";
const Order = z.object({
sku: z.string().min(1),
quantity: z.number().int().min(1).max(100)
});
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const body = await readValidatedBody(event, Order.parse);
const order = await db.orders.create({ ...body, userId: user.id });
setResponseStatus(event, 201);
return { id: order.id, status: order.status };
});
Files under server/api become API routes, and the method suffix limits the route to POST. readValidatedBody throws a 400 with details when validation fails. createError raises other HTTP errors. Return only the fields the client needs.
What a strong answer shows: Auth inside the handler, schema validation on input, explicit status codes and a deliberately shaped response.
Nitro is Nuxt's server engine, built on h3. It builds the server routes and the rendering code into an output for a deployment preset: a Node.js server, serverless functions, or edge runtimes such as Cloudflare. It also provides storage and caching utilities, such as useStorage and defineCachedEventHandler, and scheduled tasks. Nuxt 5 is planned around Nitro v3 and h3 v2.
What a strong answer shows: They know edge presets limit Node.js APIs and database drivers, and pick a preset by what the server code needs.
Throw createError with a status code. On the server, a fatal error renders the full-page error.vue with the right HTTP status. In the browser, showError shows the error page and clearError leaves it, optionally redirecting. <NuxtErrorBoundary> contains errors to one part of the page. Report errors from the app:error and vue:error hooks, or through a monitoring module, to your error tracker.
What a strong answer shows: They return real 404 and 500 status codes from server rendering instead of a 200 page that says "not found", which matters for SEO.
Use useSeoMeta for typed title, description, Open Graph and Twitter tags, and useHead for links, scripts and other head tags. Both render on the server, so crawlers see them in the HTML. Set site-wide defaults in app.vue or nuxt.config.ts and per-page values from loaded data. Nuxt 4 uses Unhead v2. Add canonical URLs, a sitemap and structured data through modules or useHead.
What a strong answer shows: They make sure metadata comes from awaited data, not lazy data that arrives after the HTML is sent.
Use @nuxt/test-utils with Vitest. Its Nuxt environment runs tests with auto-imports and plugins available, mountSuspended mounts components that use async setup and composables such as useFetch, and registerEndpoint mocks API routes. For end-to-end tests, build the app and drive it with Playwright, checking server-rendered HTML, status codes and hydration.
What a strong answer shows: Unit tests for composables, component tests with mocked endpoints, and a few end-to-end tests of critical flows against a production build.
$fetch in setup for initial data and doesn't know why requests run twice.ref.runtimeConfig.public.<ClientOnly>.useAsyncData key for unrelated data.Run a 90-minute pairing session on a prepared repo: a Nuxt 4 store with a product list, a product page, a cart and an account area. Seed it with problems: an account API route with no auth check, a module-scope ref holding the cart, a product page that calls $fetch in setup, a hydration mismatch from a formatted date, and a route rule that caches the account page. Ask the candidate to fix what matters most, explaining priorities as they go.
useFetch and checking the payload.If you need engineers who already think this way, hire senior Vue.js and Nuxt developers through Ryz. They come from the top 1% of candidates we interview, work on your team and in your repos, and work on US business hours. Our vetting process shows how we evaluate them, and you can adapt our Vue.js developer job description for a Nuxt posting.
Interview for Nuxt 4, the current line, and accept answers in Nuxt 3 vocabulary. The core ideas are the same: server rendering, the payload, data fetching composables, route rules and Nitro. A candidate strong in Nuxt 3 can move to Nuxt 4 quickly, because most changes are the directory structure and data-fetching defaults.
Vue experience covers components, reactivity and state, but Nuxt adds server rendering, hydration, the payload and a server runtime. Ask one data-fetching question and one hydration question to see whether a Vue candidate adjusts quickly.
Six to eight. Pick two on data fetching, one on rendering and hydration, one on state or configuration, one on middleware or server routes, and one on the Nuxt 4 upgrade if you are migrating. Then spend the remaining time on a short live exercise.
Questions we didn't answer? Email info@ryzlabs.com.