-
Notifications
You must be signed in to change notification settings - Fork 17
feat(nuxt): add Nuxt 4 compatibility and preserve visual editor behavior #408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Upgrade Nuxt-related dependencies to v4 and declare Nuxt 3/4 compatibility in the module meta so the package can be used in Nuxt 4 projects. Adjust the async Storyblok composable to detect the visual editor via route query and enable deep fetching to keep editor updates working correctly, and update playground configs accordingly for the new major version. Fixes WDX-140 Fixes WDX-62 Fixes #210
Ensure Nuxt auto-imports the composable from the .ts source to avoid duplicated imports and related runtime issues Fixes #331 Fixes WDX-166
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR upgrades the Nuxt integration to support Nuxt 4 while maintaining backward compatibility with Nuxt 3. The changes update dependencies to v4, declare compatibility in the module metadata, and fix visual editor behavior by detecting the editor mode via route query parameters and enabling deep fetching for proper reactivity.
Changes:
- Upgraded Nuxt dependencies from v3 to v4 across package.json files
- Added Nuxt 3/4 compatibility declaration in module metadata
- Enhanced
useAsyncStoryblokto detect visual editor mode and enable deep fetching
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/nuxt/src/runtime/composables/useAsyncStoryblok.ts | Added visual editor detection via route query and conditional deep fetching |
| packages/nuxt/src/module.ts | Declared Nuxt 3/4 compatibility and corrected file extension in import path |
| packages/nuxt/playground/package.json | Updated Nuxt dependency from v3 to v4 |
| packages/nuxt/playground/nuxt.config.ts | Disabled enableServerClient for Nuxt 4 compatibility |
| packages/nuxt/playground-e2e/package.json | Updated Nuxt dependency from v3 to v4 |
| packages/nuxt/package.json | Updated @nuxt/kit, @nuxt/schema, and nuxt dependencies to v4 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const { api, bridge, ...rest } = options; | ||
| const uniqueKey = `${stableStringify(api)}${url}`; | ||
| const route = useRoute(); | ||
| const isInVisualEditor = Boolean(route.query?.['_storyblok_tk[token]']); |
Copilot
AI
Jan 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The query parameter key '_storyblok_tk[token]' contains bracket notation which may not match the actual query parameter structure. Query parameters are typically parsed into nested objects, so this should likely be route.query?._storyblok_tk?.token instead. Verify the actual structure of the query parameter in the visual editor URL.
| () => storyblokApiInstance.get(`cdn/stories/${url}`, api), | ||
| { | ||
| ...rest, | ||
| deep: isInVisualEditor ? true : undefined, |
Copilot
AI
Jan 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ternary operator can be simplified. Since deep accepts a boolean value, you can directly assign isInVisualEditor instead of using the ternary with true : undefined.
| deep: isInVisualEditor ? true : undefined, | |
| deep: isInVisualEditor, |
| }, | ||
| devtools: true, | ||
| enableServerClient: true, | ||
| enableServerClient: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexjoverm when this is true, I get the following error (on main too!) when I run pnpm nx dev:https @storyblok/playground-nuxt and open the page https://localhost:3000:
ERROR You can't use useStoryblokApi if you're not loading apiPlugin. Please provide it on StoryblokVue initialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that's by design. When enabling server client, you're not supposed to use useAsyncStoryblok anymore
https://www.storyblok.com/mp/better-nitro-support-improved-security-nuxt-sdk
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but this means we have to disable it in the playground (like I did here) correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on February 1
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| { | ||
| ...rest, | ||
| deep: isInVisualEditor ? true : undefined, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
User-provided deep option is silently overwritten
Low Severity
The deep option from rest (which contains user-provided AsyncDataOptions) is unconditionally overwritten. When isInVisualEditor is false, deep is set to undefined, which discards any explicit deep: true or deep: false the user may have passed in options. The intended behavior to force deep: true in the visual editor is correct, but the fallback case loses user preferences instead of preserving them via rest.deep.
| name: 'useAsyncStoryblok', | ||
| as: 'useAsyncStoryblok', | ||
| from: resolver.resolve('runtime/composables/useAsyncStoryblok'), | ||
| from: resolver.resolve('runtime/composables/useAsyncStoryblok.ts'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explicit .ts extension inconsistent with other resolver paths
Medium Severity
The resolver.resolve() call for useAsyncStoryblok now includes an explicit .ts extension, while all other resolver calls in the same file (lines 50, 53, 66, 71) omit file extensions. When the module is built with nuxt-module-build, TypeScript files are compiled to .mjs, but this path would still resolve to .ts. This inconsistency could cause import resolution failures in the published package since the .ts file won't exist in the dist folder.
Upgrade Nuxt-related dependencies to v4 and declare Nuxt 3/4
compatibility in the module meta so the package can be used in Nuxt 4
projects. Adjust the async Storyblok composable to detect the visual
editor via route query and enable deep fetching to keep editor updates
working correctly, and update playground configs accordingly for the new
major version.
Fixes WDX-140
Fixes WDX-62
Fixes #210
Note
Nuxt 4 compatibility and editor behavior
nuxt,@nuxt/kit, and@nuxt/schemato v4 inpackages/nuxtand playgrounds; update playgroundpackage.jsonfiles to use Nuxt v4^3.0.0 || ^4.0.0) inmodulemeta and fix auto-import path toruntime/composables/useAsyncStoryblok.tsuseAsyncStoryblokto readuseRouteand detect Storyblok visual editor via_storyblok_tk[token], enablingdeep: trueinuseAsyncDatawhen active to preserve live updatesnuxt.config.ts(enableServerClient: false) and minor config updates for the new major versionWritten by Cursor Bugbot for commit 697328d. This will update automatically on new commits. Configure here.