VueUse is the collection of composables (useStorage, useMouse, useFetch, about two hundred of them) that many Vue projects install. Version 13.0.0 went out on 10 March 2025, five days after two betas and the last 12.8 patch. Its one breaking change is the module format: the package now ships only as ES modules (import), with no CommonJS (require()) build.
Upgrade, run your build and your tests, and you are done, unless your tests run on Jest or a Node script loads VueUse with require(). Those two setups need a fix.
One breaking change: the CommonJS build is gone
The release notes list exactly one change, under "Breaking Changes": the CommonJS build has been dropped, and the package is now ESM only. There is no list of new functions, no changed options and no removed composables. The major version changed because a new module format can break somebody's import, not because the API moved.
The package.json of @vueuse/core shows the change. In 12.8.2, the exports map sent import to index.mjs and require to index.cjs, and the main field pointed at index.cjs. In 13.0.0, the map points . (the package's main entry) straight at ./index.mjs, and the .cjs file no longer exists. vue also became a peer dependency (^3.5.0) instead of a regular one. Everything else is 12.8, shipped in one format instead of two.
ESM and CommonJS in plain words
JavaScript has two module systems. CommonJS is the older one from Node: a file reads another with require('x') and publishes values with module.exports. ES modules, ESM for short, are the standard the language itself defines: import { useStorage } from '@vueuse/core' and export function. Browsers only understand ESM. Node understands both. Bundlers understand both and translate.
For years libraries shipped two builds, so that a require() from an older tool and an import from a newer one both worked. Two builds cost maintenance and space (@vueuse/core is 1.5 MB unpacked in 12.8.2, 0.8 MB in 13.0.0), and an app can load both copies of one library at once, which breaks code that expects a single shared instance. VueUse's creator made the case for ESM only in February, in Move on to ESM-only.
Vite apps: nothing to do
If your project was created with npm create vue@latest or npx nuxi init, you are on Vite, and Vite has always consumed ESM. It pre-bundles dependencies with esbuild in development and bundles with Rollup for production, and both read index.mjs happily. Vitest runs on Vite and loads packages with import, so VueUse 13 works in your Vitest tests too. Your own code already uses import, so a composable like this one is the same on 12 and 13:
import { useStorage } from '@vueuse/core'
export function useTheme() {
return useStorage<'light' | 'dark'>('theme', 'light')
}
The Nuxt module, @vueuse/nuxt, is unaffected for the same reason: Nuxt is an ESM project end to end.
Jest: let Babel transform VueUse, or move to Vitest
Jest is the case the release breaks. Out of the box Jest runs tests as CommonJS and does not transform anything inside node_modules. When a test imports a component that imports @vueuse/core, Jest tries to require() an ESM file and stops with:
SyntaxError: Cannot use import statement outside a module
The quick fix is to let Babel transform VueUse's files. Two settings in jest.config.js do it:
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.vue$': '@vue/vue3-jest',
'^.+\\.m?[jt]s$': 'babel-jest',
},
transformIgnorePatterns: ['node_modules/(?!.pnpm|@vueuse)'],
}
transformIgnorePatternslists what Jest leaves alone, by default all ofnode_modules. The negative lookahead(?!…)turns it into "ignore everything innode_modulesexcept folders starting with@vueuse". The.pnpmpart is for pnpm, which keeps the real files undernode_modules/.pnpm/…/node_modules/: it makes the pattern skip the firstnode_modules/and test the second, as the Jest docs explain.- The
m?in thebabel-jestpattern makes it match.mjstoo. VueUse 13's entry files end in.mjs, so without it they are never transformed.
Babel then converts VueUse's files to CommonJS on the fly. That needs a project-wide config file such as babel.config.js, because a .babelrc file does not apply to files in node_modules. It works, but every ESM-only dependency you add needs another entry in that pattern.
The better fix is to stop fighting the module system. Vitest reads ESM natively and has a Jest-compatible API, so most test files need only small changes: vi.fn() in place of jest.fn(), and the globals option if your tests use describe and it without importing them. The migration guide lists the rest. If VueUse 13 is the first ESM-only package to break your Jest setup, it will not be the last, and this is a good week to migrate.
Node scripts that still use require()
The other place a require() hides is a Node script: a build step, a script that seeds data, or a hand-written server-side rendering server written in CommonJS. Node 18, Node 20 before 20.19.0 and Node 22 before 22.12 cannot require() an ES module by default, so on those versions this line throws Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported:
const { useDateFormat } = require('@vueuse/core')
Two fixes. Either turn that file into an ES module, by renaming it to .mjs or by adding "type": "module" to its package.json, and use import:
import { useDateFormat } from '@vueuse/core'
Or keep the file as CommonJS and load VueUse with a dynamic import(), which returns a promise and works in both module systems:
async function main() {
const { useDateFormat } = await import('@vueuse/core')
}
main()
Node 22.12 and later can require() an ES module directly, and so can Node 20.19.0, released on 13 March: on those versions the original line keeps working. Do not rely on that unless you control which Node version runs the script, for example in CI and on the server.
Move MaybeRef imports from VueUse to Vue
Because 13 is 12.8 in a new format, the deprecations from 12.8.0 are the ones to act on now. The types MaybeRef and MaybeRefOrGetter that VueUse exported for years are deprecated in favour of the identical types Vue itself has shipped since 3.3. Change the import, and nothing else:
import { computed, toValue, type MaybeRefOrGetter } from 'vue'
export function useGreeting(name: MaybeRefOrGetter<string>) {
return computed(() => `Hello, ${toValue(name)}`)
}
toValue() unwraps a ref, calls a getter, or returns a plain value as is, which is what makes a MaybeRefOrGetter argument convenient for callers. Both the type and the helper come from vue, so a composable written this way has no VueUse import at all.
Upgrade and check that it worked
Upgrade every @vueuse/* package you use to the same major, because @vueuse/components, @vueuse/integrations, @vueuse/router and @vueuse/nuxt share @vueuse/core's version number. Leave out @vueuse/nuxt if you are not on Nuxt:
pnpm add @vueuse/core@13 @vueuse/nuxt@13
Then run a three-line check from the project's root folder. The first line proves the package loads as ESM from Node, the second proves your bundler is happy, the third proves your tests are:
node -e "import('@vueuse/core').then(m => console.log(typeof m.useStorage))"
pnpm build
pnpm test
The first command should print function. If the build and the tests pass too, there is nothing else in this release to look at. If pnpm test is the one that fails, go back to the Jest section.