Describe the bug
Running Histoire with @histoire/plugin-nuxt can make type-aware ESLint fail on valid Nuxt application code even though no application source or lint configuration changed.
After normal Nuxt preparation, ESLint resolves auto-imports such as definePageMeta, useNuxtApp, and useState, and lint passes. After histoire build, the same lint command reports @typescript-eslint/no-unsafe-* errors because those auto-imports can no longer be resolved from the generated declarations. Running normal Nuxt preparation again makes the errors disappear.
The result therefore depends on command order rather than source code:
nuxt prepare -> lint passes
histoire build -> lint fails
nuxt prepare -> lint passes again
This is a problem because .nuxt is part of the type environment consumed by type-aware ESLint. A successful Histoire command changes that environment for later application commands, so the same commit can pass or fail lint depending on which command ran first. This can produce CI failures, break local pre-commit lint after using Histoire, and report cascades of unsafe operations in unchanged application files.
@histoire/plugin-nuxt creates an auxiliary Nuxt instance for story rendering. That instance intentionally disables pages and replaces useNuxtApp with a Histoire-specific implementation, but it writes its generated declarations into the host application's normal .nuxt directory. Histoire's declarations remain there after Histoire exits, so subsequent application lint commands consume the wrong generated type environment.
Expected behavior: running Histoire does not change the result of a subsequent application lint when no application source or configuration changed. The auxiliary Nuxt instance should use an isolated build directory or otherwise preserve the host application's generated state.
The following application-side workaround prevents the failure:
export default defineNuxtConfig({
buildDir: process.env.HISTOIRE === 'true' ? '.nuxt/histoire' : '.nuxt',
})
Histoire sets process.env.HISTOIRE to 'true'. With this workaround, Histoire writes under .nuxt/histoire, the application's .nuxt/imports.d.ts remains unchanged, and lint still passes immediately after the Histoire build.
Reproduction
https://github.com/costleya/histoire-nuxt-build-dir-repro
The repository contains one Nuxt page using definePageMeta, one Histoire story, and one type-aware ESLint rule. It does not contain the build-directory workaround.
Run:
pnpm install --frozen-lockfile
pnpm repro
The reproduction script verifies this sequence:
pnpm prepare
pnpm lint
pnpm story:build
pnpm lint
The first lint succeeds. Histoire successfully builds one story. The second lint then fails on the unchanged definePageMeta() call:
Unsafe call of a type that could not be resolved @typescript-eslint/no-unsafe-call
The script exits successfully only after confirming both the generated declaration change and this exact lint failure.
Before Histoire runs, .nuxt/imports.d.ts contains the normal application declarations, including:
export { useNuxtApp, tryUseNuxtApp, defineNuxtPlugin, definePayloadPlugin, useRuntimeConfig, defineAppConfig } from '#app/nuxt'
export { definePageMeta, PageMeta } from '#app/composables/pages'
After Histoire runs, the same file instead contains:
export { useNuxtApp } from '#build/histoire/composables.mjs'
The definePageMeta declaration is absent. ESLint therefore cannot resolve the auto-imported call's type and reports it as unsafe.
System Info
System:
OS: macOS 26.5.2
CPU: (10) arm64 Apple M4
Memory: 110.45 MB / 24.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.18.0 - /Users/costleya/.nvm/versions/node/v24.18.0/bin/node
npm: 11.16.0 - /Users/costleya/.nvm/versions/node/v24.18.0/bin/npm
pnpm: 10.16.1 - /opt/homebrew/bin/pnpm
bun: 1.3.14 - /opt/homebrew/bin/bun
Browsers:
Chrome: 151.0.7922.138
Safari: 26.5.2
npmPackages:
@histoire/plugin-nuxt: 1.0.0-beta.1 => 1.0.0-beta.1
@histoire/plugin-vue: 1.0.0-beta.1 => 1.0.0-beta.1
histoire: 1.0.0-beta.1 => 1.0.0-beta.1
Used Package Manager
pnpm
Validations
Describe the bug
Running Histoire with @histoire/plugin-nuxt can make type-aware ESLint fail on valid Nuxt application code even though no application source or lint configuration changed.
After normal Nuxt preparation, ESLint resolves auto-imports such as definePageMeta, useNuxtApp, and useState, and lint passes. After histoire build, the same lint command reports @typescript-eslint/no-unsafe-* errors because those auto-imports can no longer be resolved from the generated declarations. Running normal Nuxt preparation again makes the errors disappear.
The result therefore depends on command order rather than source code:
This is a problem because .nuxt is part of the type environment consumed by type-aware ESLint. A successful Histoire command changes that environment for later application commands, so the same commit can pass or fail lint depending on which command ran first. This can produce CI failures, break local pre-commit lint after using Histoire, and report cascades of unsafe operations in unchanged application files.
@histoire/plugin-nuxt creates an auxiliary Nuxt instance for story rendering. That instance intentionally disables pages and replaces useNuxtApp with a Histoire-specific implementation, but it writes its generated declarations into the host application's normal .nuxt directory. Histoire's declarations remain there after Histoire exits, so subsequent application lint commands consume the wrong generated type environment.
Expected behavior: running Histoire does not change the result of a subsequent application lint when no application source or configuration changed. The auxiliary Nuxt instance should use an isolated build directory or otherwise preserve the host application's generated state.
The following application-side workaround prevents the failure:
Histoire sets process.env.HISTOIRE to 'true'. With this workaround, Histoire writes under .nuxt/histoire, the application's .nuxt/imports.d.ts remains unchanged, and lint still passes immediately after the Histoire build.
Reproduction
https://github.com/costleya/histoire-nuxt-build-dir-repro
The repository contains one Nuxt page using definePageMeta, one Histoire story, and one type-aware ESLint rule. It does not contain the build-directory workaround.
Run:
The reproduction script verifies this sequence:
The first lint succeeds. Histoire successfully builds one story. The second lint then fails on the unchanged definePageMeta() call:
Unsafe call of a type that could not be resolved @typescript-eslint/no-unsafe-call
The script exits successfully only after confirming both the generated declaration change and this exact lint failure.
Before Histoire runs, .nuxt/imports.d.ts contains the normal application declarations, including:
After Histoire runs, the same file instead contains:
The definePageMeta declaration is absent. ESLint therefore cannot resolve the auto-imported call's type and reports it as unsafe.
System Info
Used Package Manager
pnpm
Validations