{"record":{"id":"24f3e196e668b9f3","repo":"vuejs/core","slug":"vue-compiler-sfc-modules-option-can-only-be-u","errorCode":null,"errorMessage":"[@vue/compiler-sfc] `modules` option can only be used with compileStyleAsync().","messagePattern":"\\[@vue/compiler-sfc\\] `modules` option can only be used with compileStyleAsync\\(\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/compiler-sfc/src/compileStyle.ts","lineNumber":129,"sourceCode":"  const longId = `data-v-${shortId}`\n\n  const plugins = (postcssPlugins || []).slice()\n  plugins.unshift(cssVarsPlugin({ id: shortId, isProd }))\n  if (trim) {\n    plugins.push(trimPlugin())\n  }\n  if (scoped) {\n    plugins.push(scopedPlugin(longId))\n  }\n  let cssModules: Record<string, string> | undefined\n  if (modules) {\n    if (__GLOBAL__ || __ESM_BROWSER__) {\n      throw new Error(\n        '[@vue/compiler-sfc] `modules` option is not supported in the browser build.',\n      )\n    }\n    if (!options.isAsync) {\n      throw new Error(\n        '[@vue/compiler-sfc] `modules` option can only be used with compileStyleAsync().',\n      )\n    }\n    plugins.push(\n      postcssModules({\n        ...modulesOptions,\n        getJSON: (_cssFileName: string, json: Record<string, string>) => {\n          cssModules = json\n        },\n      }),\n    )\n  }\n\n  const postCSSOptions: ProcessOptions = {\n    ...postcssOptions,\n    to: filename,\n    from: filename,\n  }","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/vuejs/core/blob/a2b40db9a83b36ed9da3a16403cf8f040262d73f/packages/compiler-sfc/src/compileStyle.ts#L111-L147","documentation":"Thrown by doCompileStyle in @vue/compiler-sfc when the `modules` option is used with the synchronous compileStyle() entry point. postcss-modules resolves asynchronously (it writes a JSON map via getJSON), so Vue requires the caller to use compileStyleAsync and to set options.isAsync. The guard at compileStyle.ts:129 fires after the browser-build check, before postcssModules is registered.","triggerScenarios":"Calling compileStyle({ modules: true }) (the sync function) instead of compileStyleAsync({ modules: true }). Using any wrapper that internally calls the sync compileStyle while forwarding a modules flag.","commonSituations":"Migrating a style pipeline from sync to async but forgetting to switch the entry function; a custom integration that re-exports compileStyle under a different name.","solutions":["Switch to compileStyleAsync() and await its result when you need CSS Modules.","If you do not actually need CSS Modules, remove the `modules: true` option and keep using the sync compileStyle().","In integration code, branch on the presence of the modules option to pick the correct entry point."],"exampleFix":"// before\nimport { compileStyle } from '@vue/compiler-sfc'\nconst res = compileStyle({ filename: 'x.css', source, modules: true })\n\n// after\nimport { compileStyleAsync } from '@vue/compiler-sfc'\nconst res = await compileStyleAsync({ filename: 'x.css', source, modules: true })","handlingStrategy":"validation","validationCode":"// Route the modules option to the async entry automatically.\nasync function compileStyleSmart(opts) {\n  if (opts.modules) {\n    return compileStyleAsync({ ...opts, isAsync: true })\n  }\n  return compileStyle(opts)\n}","typeGuard":"function needsAsyncCompile(opts: unknown): opts is { modules: true } {\n  return !!opts && (opts as any).modules === true\n}","tryCatchPattern":null,"preventionTips":["Always use compileStyleAsync when your options may include modules.","Branch your wrapper on the modules flag so the sync entry is never used for CSS Modules.","Treat `modules: true` as an async contract in your pipeline."],"tags":["vue","compiler-sfc","css-modules","async","configuration"],"backgroundTag":null,"analyzedSha":"a2b40db9a83b36ed9da3a16403cf8f040262d73f","analyzedAt":"2026-08-12T14:21:14.989Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}