Windows: externals plugin bundles a package imported by absolute path, duplicating it (`pinia` → "no active Pinia" on Nuxt 4.6)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 83/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- node.js, typescript
Research direction
Start in Nitro's externals implementation at the pkgName !== originalId branch described in the issue, and inspect the two path comparisons against id. Reproduce the Windows slash mismatch with the reported Nuxt and Pinia setup, then verify that the fix externalizes the absolute-path import and the SSR request returns 200 with count: 1.
Written by the indexing model from the issue text.
Description
Environment
- OS: Windows 11 (x64)
- Node.js 24.21.0, pnpm 12.11.2
- nitropack 2.13.4 (node-server preset)
- Nuxt 4.6.1 (also 4.6.0), Vite 8.3.4, Vue 3.5.43
- pinia 4.0.3, @pinia/nuxt 1.0.2
Reproduction
A fresh Nuxt app with one Pinia store. No other modules or config.
package.json
{
"private": true,
"type": "module",
"scripts": { "build": "nuxt build", "preview": "node .output/server/index.mjs" },
"dependencies": {
"@pinia/nuxt": "1.0.2",
"nuxt": "4.6.1",
"pinia": "4.0.3",
"vue": "3.5.43"
}
}
nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: '2026-10-01',
modules: ['@pinia/nuxt'],
})
app/stores/counter.ts
import { defineStore } from 'pinia'
export const useCounter = defineStore('counter', {
state: () => ({ count: 1 }),
})
app/pages/index.vue
<script setup lang="ts">
import { useCounter } from '../stores/counter'
const counter = useCounter()
</script>
<template>
<p>count: {{ counter.count }}</p>
</template>
app/app.vue
<template>
<NuxtPage />
</template>
Steps (on Windows):
pnpm install
pnpm build
node .output/server/index.mjs
curl http://localhost:3000/
Describe the bug
Expected
200 with <p>count: 1</p>.
Actual
500 on every SSR request:
[🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?
nuxt dev is not affected. The same app on Nuxt 4.5.2 (same Nitro 2.13.4) renders fine.
Additional context
Cause
The Vite SSR bundle (.nuxt/dist/server/server.mjs) imports pinia twice, by two different specifiers:
import { createPinia, setActivePinia, shouldHydrate } from "C:/…/node_modules/.pnpm/[email protected]_…/node_modules/pinia/dist/pinia.js" // from @pinia/nuxt's plugin
import { defineStore } from "pinia" // from the app's store
Nitro's externals plugin keeps the bare pinia external, but bundles the absolute-path import into chunks/virtual/entry.mjs. The server then runs two pinia module instances: the plugin activates one, the store reads the other.
It bundles it because of this check in externals (dist/rollup/index.mjs, the pkgName !== originalId branch for an absolute originalId):
const packageEntry = tryResolve(pkgName, importer);
if (packageEntry !== id) {
const guessedSubpath = await lookupNodeModuleSubpath(id).catch(() => null);
const resolvedGuess = guessedSubpath && tryResolve(join(pkgName, guessedSubpath), importer);
if (resolvedGuess === id) {
// … external
}
return null; // → bundled
}
id is normalize(originalId), so it uses forward slashes. tryResolve returns fileURLToPath(...), which uses backslashes on Windows. The two strings name the same file but can never be equal. Logged values for the pinia import above:
{
"id": "C:/…/node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/pinia/dist/pinia.js",
"packageEntry": "C:\\…\\node_modules\\.pnpm\\[email protected]_@[email protected][email protected]\\node_modules\\pinia\\dist\\pinia.js"
}
So on Windows, with the default externals settings (tracing on, no matching inline/external rule), a package imported by absolute path is bundled instead of externalized. It only breaks at runtime when the same package is also imported by its bare name somewhere else and keeps module-level state, as pinia does.
Fix (verified locally)
Normalize before comparing. With this change the repro builds a single external pinia (no createPinia in entry.mjs) and GET / returns 200 with count: 1:
- if (packageEntry !== id) {
+ if (normalize(packageEntry || "") !== id) {
const guessedSubpath = await lookupNodeModuleSubpath(id).catch(() => null);
const resolvedGuess = guessedSubpath && tryResolve(join(pkgName, guessedSubpath), importer);
- if (resolvedGuess === id) {
+ if (resolvedGuess && normalize(resolvedGuess) === id) {
(normalize is already imported from pathe in that file.)
Related
- nuxt/nuxt#36467 and nitrojs/nitro#4732 are the same class of bug: string inline rules versus backslash ids. #4732 normalizes the id only inside the string matcher, so it does not cover this comparison.
- Nuxt-side context: on 4.6 the
@pinia/nuxtplugin'spiniaimport reaches Nitro as an absolute path (on 4.5.2 it is bare"pinia"), which is what exposes this. Absolute imports of other packages (hookable, unctx, ufo, …) already occur on 4.5.2. - Workaround:
nitro.externals.inline: ['pinia', /[\\/]node_modules[\\/]pinia[\\/]/](bundles both imports into one copy).
- Dominant language
- TypeScript
- Stars
- 11.3k
- Forks
- 936
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 170
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from nitrojs/nitro
-
v2: backport #4683 (a package's own entry is externalized as `pkg/`, DEP0155)Possibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 71/100
Maintainers usually reply within 1 day
-
documentation v3
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
bug v2
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
nitrojs/nitro#4555 · 5 comments ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
cli performance v3
Difficulty 4/5 3-5 days Newbie friendliness 54/100
nitrojs/nitro#4939 · 1 reaction ·
Maintainers usually reply within 1 day
Similar issues
-
category::task code priority::normal
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
e0ipso/strikethroo#145 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 85/100
cocos/cocos-docs#3333 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
RevenueCat/react-native-purchases#2079 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
RevenueCat/purchases-hybrid-common#2039 ·
Maintainers usually reply within 1 day
-
agent-reported area/react bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day