skip to content

Auto-Imports & Composables

Nuxt imports composables, utils and components for you, and useState keeps state that survives hydration. Interviewers ask what auto-imports cost and why useState exists at all.

on this pageshow

explore

questions

5

In a Nuxt 4 app, what is auto-imported without an import line, and how does a function in app/composables/ get picked up?

level: juniorimportance: must knowfreq 50%

answer

  1. three scanned app directories
  2. Vue and Nuxt APIs come built in
  3. top-level files only
  4. default export takes the file name
  5. #imports and .nuxt/imports.d.ts

basics

~20 s

Nuxt 4 auto-imports Vue APIs like ref, its own composables like useState, components from app/components/, and the exports of top-level files in app/composables/ and app/utils/. The build injects real imports only where a file uses those names.

solid answer

~40 s

Nuxt 4 auto-imports three kinds of names into app code: Vue's APIs such as `ref`, `computed` and `watch`; Nuxt's own composables such as `useState`, `useRoute` and `navigateTo`; and your code from `app/composables/`, `app/utils/` and `shared/utils/`, plus components from `app/components/`. For composables, Nuxt scans only the top-level files of `app/composables/`: each named export keeps its name, and a default export is named after the file in camelCase, so `use-cart-total.ts` gives `useCartTotal`. Files in subfolders are skipped unless re-exported from `index.ts` or added through `imports.dirs`. These aren't globals: at build time Nuxt inserts real `import` statements for exactly the names a file uses, so unused code is still tree-shaken, and `#imports` exposes the same bindings explicitly.

code

ts · 7 lines
ts
// app/composables/useCounter.ts: named export -> useCounter()
export const useCounter = () => useState('counter', () => 0)

// app/utils/format-price.ts: default export -> formatPrice()
export default function (cents: number) {
  return (cents / 100).toFixed(2)
}

go deeper

for a junior

Recall the sources: Vue APIs, Nuxt composables, and the top-level files of app/composables/, app/utils/ and app/components/.

for a middle

Explain the scanning rules: named versus default exports, why nested folders are skipped, and how #imports and generated types fit in.

for a senior

Show where auto-imports stop: server code, node_modules and call sites that need the Nuxt context, and how that shapes code layout in a large app.

for a principal

Weigh convenience against explicitness for a big team: which code should stay auto-imported and which should require explicit imports.

## What arrives without an import line In a Nuxt 4 app, most names you use in `app/` code are **auto-imported**: you write `ref(0)` or `useState('counter')` and never add an `import` statement. The sources are fixed: | Source | Examples | Where usable | |---|---|---| | Vue APIs | `ref`, `computed`, `watch`, `onMounted` | app code | | Nuxt composables and utils | `useState`, `useRoute`, `navigateTo`, `useNuxtApp` | app code | | `app/composables/` | your `useCounter` | app code | | `app/utils/` | your `formatPrice` | app code | | `shared/utils/`, `shared/types/` | code shared by app and server | app and server | | `app/components/` | `<AppHeader />`, `<BaseFooButton />` | templates | | module presets | composables a module registers | app code | `app/composables/` and `app/utils/` are scanned the same way. The split between them is a semantic one: stateful composables in one, plain helpers in the other. ## How app/composables/ is scanned 1. Nuxt scans the **top-level files** of `app/composables/` and `app/utils/`. 2. Every **named export** of those files becomes an auto-import under its own name: `export const useCounter = () => ...` gives `useCounter`. 3. A **default export** is registered under the camelCase form of the file name: `use-cart-total.ts` or `useCartTotal.ts` gives `useCartTotal`. 4. Files in **subfolders are not scanned**. `app/composables/cart/useCart.ts` stays invisible until you re-export it from `app/composables/index.ts` or add the folder through `imports.dirs`. 5. The dev server picks up new top-level files, and adding or removing a scanned directory restarts it. ## A build transform, not globals Auto-imports are **not global variables**. At build time Nuxt's transform looks at each file, finds the auto-importable names it uses, and inserts real `import` statements for exactly those names. Consequences: - **Tree-shaking still works.** A composable nobody uses never enters the bundle; the docs stress that only what is used reaches production code. - **Types come from generated files.** Nuxt writes `.nuxt/imports.d.ts`. Until `nuxt prepare`, `nuxt dev` or `nuxt build` has run, the editor reports errors such as `Cannot find name 'useBar'`. - **Every auto-import has an explicit path too.** `import { ref, useState } from '#imports'` names the same bindings, which helps when reading unfamiliar code or when auto-imports are switched off. - **Files in `node_modules` get no injected imports**, so a published library must import what it uses. ## Components follow their own rules - A component's name comes from its path, with duplicate segments removed: `app/components/base/foo/Button.vue` is `<BaseFooButton />`. `pathPrefix: false` in the `components` config registers it as `<Button />` instead. - The `Lazy` prefix, as in `<LazyMountainsList />`, loads the component's code only when it renders. - For `<component :is>`, use `resolveComponent('MyButton')` with a literal string, or import the component from `#components`. ## Where auto-imports do not reach - **`server/` code** gets Nitro's own auto-imports, `server/utils/` plus `shared/`; `app/composables/` and `app/utils/` are not visible there. - **Context-bound composables** still need a valid call site. Most Nuxt composables must run in `setup`, a plugin or route middleware, whether they were imported automatically or by hand. ## Common mistakes - Expecting a composable in a nested folder to be found. - Treating auto-imports as globals that inflate every bundle. - Importing app composables into `server/` code. - Blaming Nuxt for missing types before the dev server has generated them. ## Checking what a name resolves to When a name behaves unexpectedly, look at what Nuxt generated rather than guessing: - **`.nuxt/imports.d.ts`** declares every auto-imported name together with the file or package it comes from, so a missing or surprising source is visible at a glance. - **Go to definition** in an editor follows those generated declarations to the real file. - **An explicit import** of the same name, from a file path or from `#imports`, removes all doubt in the file where it matters. This habit matters most in larger apps, where several folders, layers and modules all contribute names to the same pool.

  • How would you auto-import Pinia-style stores kept in app/stores/?
    Add the folder to `imports.dirs`, for example `imports: { dirs: ['stores'] }` in `nuxt.config.ts`. The option only adds directories; `composables/` and `utils/` stay scanned. The same scanning rules apply: top-level files, named exports by name, and default exports by camelCase file name.
  • A colleague asks whether auto-imports make the bundle bigger. What do you answer?
    No. They are a build-time transform, not globals: for each file Nuxt inserts `import` statements only for the auto-importable names that file actually uses. Unused composables never get imported, so the bundler tree-shakes them as usual. The cost is elsewhere: less explicit code and a risk of name collisions.

saying these in an interview costs you the question

  • Auto-imports are registered as global variables on window
  • Every file in any subfolder of composables/ is auto-imported
  • Only default exports from composables/ are auto-imported
  • Auto-imported composables are bundled even when unused
  • app/utils/ helpers are auto-imported into server/ handlers too
open as a page

In Nuxt 4, why does a counter seeded with a random value in a plain ref mismatch after hydration, while useState keeps the server's value?

level: middleimportance: must knowfreq 48%

basics

~20 s

A plain ref(Math.random()) is re-created when setup runs again in the browser, so the client value differs from the server HTML. useState stores its value in the Nuxt payload; during hydration it finds that value and skips the initialiser, so both sides match.

open as a page

In Nuxt 4, what key does useState get when you omit one, what if two composables reuse a key, and which values can it hold?

level: middleimportance: should knowfreq 34%

basics

~20 s

Without a key, Nuxt's compiler appends one unique to that call site in the file. Two calls with the same key share one state, even from unrelated composables. Values must survive payload serialisation: no class instances, functions or symbols.

open as a page

In a Nuxt 4 team app, two files in app/composables/ both export useCart. What breaks, and how do you scope or disable auto-imports to fix it?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Two exports named useCart give Nuxt 4's auto-import two candidates for one name, so a page can get the other team's version. Rename by domain or import explicitly; imports.scan: false stops scanning your folders, and imports.autoImport: false turns injection off.

open as a page

In Nuxt 4, what does useNuxtApp() give a composable, and why can a Nuxt composable called after an await throw NUXT_E1001 during SSR only?

level: seniorimportance: should knowfreq 31%

basics

~20 s

useNuxtApp() returns the current Nuxt app: vueApp, provided helpers, hooks, payload, ssrContext and isHydrating. On the server each request has its own app tracked implicitly; after an await in a plain function that tracking is lost, so the next Nuxt composable throws NUXT_E1001.

open as a page