skip to content

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%

answer

  1. one name, one source
  2. built-in shadowing is warned
  3. layers carry priority
  4. scan versus autoImport
  5. explicit imports end ambiguity

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.

solid answer

~40 s

Auto-imports map each name to one source, so two `useCart` exports in `app/composables/` leave a page bound to whichever candidate the resolver picks, not the one its author meant. Nuxt's composable diagnostic, `NUXT_B6002`, only fires when an export shadows a built-in such as `useFetch`; duplicate component names do get a `NUXT_B3011` warning. So the bug tends to show up as wrong behaviour rather than a Nuxt diagnostic. The fixes, in order: rename by domain (`useCheckoutCart`, `useWishlistCart`); import explicitly from `~/composables/checkout` where it matters; move feature internals into subfolders, which are not scanned, and re-export only the public names. For scope: `imports.dirs` only adds folders, `imports.scan: false` stops scanning your own composables and utils but keeps Vue and Nuxt APIs, and `imports.autoImport: false` disables auto-imports entirely while `#imports` still works.

code

ts · 8 lines
ts
// nuxt.config.ts
export default defineNuxtConfig({
  imports: {
    // keep ref, computed, useState, navigateTo...
    // but stop scanning app/composables/ and app/utils/
    scan: false,
  },
})

go deeper

for a junior

Recall that every auto-imported name must be unique, and that an explicit import always states which file a function comes from.

for a middle

Explain the scanning and priority rules: top-level files, layer precedence, and which collisions Nuxt warns about.

for a senior

Diagnose a silent collision, fix it by renaming or explicit imports, and choose among imports.dirs, imports.scan and imports.autoImport knowingly.

for a principal

Decide how much implicitness a multi-team codebase can afford, and back that with naming rules, layer boundaries and review checks.

## How a name becomes a binding Nuxt 4 collects every export from the top-level files of `app/composables/` and `app/utils/`, adds Vue's and Nuxt's own APIs and whatever modules register, and hands the list to its import transform. Each file that uses a name gets an `import` for it. That only works while **one name maps to one source**. ## The collision Two teams add `app/composables/checkout.ts` and `app/composables/wishlist.ts`, and both export `useCart`: - the auto-import list now holds two candidates for one identifier, and a page can be given only one of them; - which one wins is decided by the resolver's ordering and priorities, not by the intent of the page's author; - Nuxt's collision diagnostic for composables, `NUXT_B6002`, fires only when your export shadows a **Nuxt built-in** such as `useFetch` or `useState`; two app composables sharing a name are not flagged by it; - components are handled differently: two components resolving to one name produce a duplicate-component warning, `NUXT_B3011`, and a configured `priority` decides which is registered. So one team's pages can end up running the other team's composable, and the bug surfaces as wrong behaviour rather than as a Nuxt diagnostic. ## Layers and priority When the same name comes from different **layers**, priority is deliberate: the project outranks the layers it extends, which is how a project overrides a layer's composable on purpose. Within one layer, same-named exports have no such tie-breaker. Module authors can also set an explicit `priority` on the imports and components they add. ## Fixing it 1. **Rename by domain.** `useCheckoutCart` and `useWishlistCart` remove the ambiguity at the source. 2. **Import explicitly where it matters.** `import { useCart } from '~/composables/checkout'` makes the binding unambiguous in that file. 3. **Group code per feature** in subfolders, which are not scanned, and re-export from a top-level file only the names meant to be global. 4. **Split domains into layers** when teams own separate areas, so that precedence is explicit. ## Scoping and disabling auto-imports | Setting | Effect | |---|---| | `imports.dirs: ['stores']` | **adds** directories to scan; the defaults stay | | `imports.scan: false` | stops scanning your own `composables/` and `utils/`; Vue, Nuxt and module imports stay | | `imports.autoImport: false` | turns auto-imports off entirely; explicit imports from `#imports` still work | | `components.dirs: []` | stops auto-registering your own components; module components stay | | `pathPrefix: false` | registers components by file name only, which makes collisions **more** likely | The docs warn that `imports.scan: false` also breaks the layer override feature, because each layer's composables must then be imported explicitly. A layer can also set `imports.scan: false` in its own config to keep its composables out of the scan. ## Team conventions that prevent it - Prefix composables with their domain, and keep one owner per exported name. - Keep shared, app-wide composables in top-level files, and feature internals in subfolders with explicit imports. - Never reuse a built-in's name: `NUXT_B6002` exists because overriding one will likely cause issues. - Remember that auto-imports are not injected into files in `node_modules`, so shared packages must import what they use. ## Detecting a collision Because no Nuxt diagnostic covers a same-layer clash between two app composables, make it visible yourself: - add a check in CI that lists the exports of the top-level files in `app/composables/` and `app/utils/` and fails on duplicates; - when a name misbehaves, open `.nuxt/imports.d.ts`, which declares each auto-imported name with the source it resolved to, and compare it with the file the page's author meant; - in review, treat a new top-level export as a public, app-wide name, and ask whether it should be one. ## When to turn auto-imports down Small apps rarely need to. The case for `imports.scan: false` grows with team count: explicit imports make a file's dependencies visible in review, and moving a composable becomes a refactor a tool can check rather than a name a scanner may or may not find. The cost is boilerplate and losing layer overrides, so many teams keep the scan and enforce domain prefixes instead.

  • What does Nuxt do if a developer adds their own useFetch in app/composables/?
    It reports `NUXT_B6002`: the name is already auto-imported as a Nuxt built-in, and overriding it will likely cause issues. The fix it suggests is renaming the export. Shadowing a built-in is worse than a local collision, because every page and module expecting Nuxt's composable could receive the app's version.
  • What changes between imports.scan: false and imports.autoImport: false?
    `scan: false` stops Nuxt from scanning your own `composables/` and `utils/`, while Vue, Nuxt and module-provided names stay auto-imported. `autoImport: false` switches off automatic injection entirely, so even `ref` must be imported, though every name remains importable explicitly from `#imports`.

saying these in an interview costs you the question

  • Nuxt fails the build whenever two composables share a name
  • imports.dirs replaces the default composables/ and utils/ folders
  • imports.scan: false also stops ref and computed from being auto-imported
  • imports.autoImport: false makes #imports unusable
  • Defining your own useFetch in composables/ is a supported override
  • pathPrefix: false reduces component name collisions