Skip to content

Add support for Nuxt 4 - #13

Merged
marcospassos merged 1 commit into
masterfrom
nuxt-4-support
Sep 11, 2026
Merged

marcospassos merged 1 commit into
masterfrom
nuxt-4-support

Conversation

@marcospassos

Copy link
Copy Markdown
Member

Summary

Installing @croct/plug-nuxt in a Nuxt 4 app fails with ERESOLVE, since the peer range is still nuxt@^3.0.0. This widens the range, fixes the Nuxt 4 regressions that widening exposed, and moves the test toolchain to Nuxt 4 while keeping Nuxt 3 covered in CI.

Changes

Support

  • peerDependencies.nuxt: ^3.0.0 → ^3.0.0 || ^4.0.0
  • dependencies["@nuxt/kit"]: ^3.17.5 → ^3.17.5 || ^4.0.0. Left at ^3, npm nests a second kit under the module; widened, it dedupes to the host's copy.

Nuxt 4 fix

Nuxt 4 changed the default of useAsyncData's data and error from null to undefined. Personalization checked error.value !== null before anything else, so every successful render took the error branch and threw a 500 (Cannot read properties of undefined (reading 'message')). Slot had the same check but escaped because it tests data first. Both now treat undefined as no error.

Tests

  • @nuxt/test-utils 3 → 4 and vitest 3 → 4; the nuxt dev dependency moves to ^4.5.2. With test-utils 3, the nuxt vitest environment fails to boot under Nuxt 4.
  • Four test files read the runtime config in the describe body. Nuxt 4 has no app instance while the module evaluates, so they now do it in beforeAll.
  • The e2e job runs as a matrix over Nuxt 3 and 4. The lockfile pins Nuxt 4, and the Nuxt 3 leg downgrades nuxt and @nuxt/kit together; downgrading only nuxt leaves kit 4 hoisted, so the module would run kit 4 against a Nuxt 3 app.
  • Added a waitForHydration() e2e helper. Clicking a NuxtLink before hydration performs a full document navigation, so the client-navigation specs were asserting the server-rendered context instead. Nuxt 4 dev hydrates slowly enough to expose this; identity.spec.ts had the same race and was passing for the wrong reason.

Tooling

  • @nuxt/module-builder 0.8 → 1.0. v1 drops the Nuxt-3-only kit peer. It emits the module entry declarations as .d.mts, so exports["."].types and types point there now. The runtime declarations are still .d.ts, so ./types and ./csr are unchanged.
  • prepare is no longer published. npm runs it for git and tarball installs, where e2e/app doesn't exist. tsc needs the generated #imports types, so validate now runs nuxi prepare e2e/app itself, and dev:prepare stays for editor support.
  • Lint was failing on master. @croct/eslint-plugin sets no import resolver, so import-x falls back to the legacy node one, which isn't installed. Its last fallback resolves <package>/node as a path, and once no-cycle walked into node_modules, it loaded lightningcss/node (native bindings) as the resolver and crashed the run. An explicit eslint-import-resolver-typescript resolver fixes it, and also resolves #app and #imports through the tsconfig paths.

Verification

Nuxt 4 (lockfile) Nuxt 3 (matrix leg)
lint pass —
validate pass —
unit (115) pass —
build pass pass
e2e (69) pass pass
prerender e2e (3) pass pass

Both Nuxt 4 e2e failures were reproduced first, and both specs were confirmed to pass on Nuxt 3, so neither was a flake.

Type resolution after the .d.mts move was checked against the packed tarball in a scratch consumer: imports of @croct/plug-nuxt, /types and /csr typecheck under both moduleResolution: bundler and nodenext. A negative control ({appId: 42}) fails as expected, so the types are really loaded rather than falling back to any.

End to end, the packed tarball installs into a Nuxt 4.5.2 app without overrides or ERESOLVE, with a single @nuxt/kit, and the app builds with both /api/_croct/* handlers registered.

Notes

  • Unit tests run on Nuxt 4 only; test-utils 4 doesn't support Nuxt 3. Nuxt 3 keeps full e2e coverage.
  • nuxt@4.5.2 requires Node ^22.19.0 || ^24.11.0. On older Node, ^4.0.0 silently resolves to 4.4.5, whose exact kit pin nests a second @nuxt/schema and breaks tsc. That's why the dev dependency is pinned to ^4.5.2. CI's Node 22 is unaffected.
  • import-x/no-cycle runs now, but it doesn't report a planted two-file cycle, even with maxDepth raised. That's a limitation of the import-x build bundled into @croct/eslint-plugin, not something this PR can fix.

Widen the nuxt peer range to ^3.0.0 || ^4.0.0 and the @nuxt/kit
dependency to ^3.17.5 || ^4.0.0, so kit dedupes to the copy the host
application already installs instead of nesting a second major.

Nuxt 4 changed the default of useAsyncData's data and error from null
to undefined. Slot and Personalization compared error strictly against
null, so a successful Personalization rendered its error slot with an
undefined error and failed with a 500. Both now treat undefined as no
error.

Tests:

- Upgrade @nuxt/test-utils to v4 and vitest to v4, and move the
  development dependency on nuxt to 4.5.2. Tests that read the runtime
  config at describe scope now do it in beforeAll, as Nuxt 4 has no app
  instance while the module body evaluates.
- Run the e2e suite against both Nuxt 3 and Nuxt 4, pinning @nuxt/kit
  alongside nuxt so the Nuxt 3 leg does not load kit 4.
- Wait for hydration before clicking links in the specs that assert a
  client-side navigation. Clicking earlier performs a full navigation,
  so those specs were asserting the server context instead.

Tooling:

- Upgrade @nuxt/module-builder to v1, which drops its nuxt-3-only kit
  peer. It emits the module declarations as .d.mts, so the type entry
  points move accordingly.
- Stop publishing the prepare script. npm runs it for git and tarball
  installs, where e2e/app does not exist. The validate script now
  prepares the types it needs itself, and dev:prepare remains for
  editor support.
- Configure an explicit import resolver for ESLint. Without one,
  import-x falls back to the legacy node resolver, which is not
  installed, and no-cycle walking into node_modules loaded
  lightningcss/node as the resolver, crashing the lint run.
@marcospassos marcospassos added the enhancement Improvement of existing features label Sep 11, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@croct/plug-nuxt@13

commit: 3b96eb2

@marcospassos
marcospassos merged commit 72f5ced into master Sep 11, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement of existing features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant