vuejs/vue · error · Error
[@vue/compiler-sfc] <script> and <script setup> must have th
Error message
[@vue/compiler-sfc] <script> and <script setup> must have the same language type.
What it means
Thrown when an SFC has both a normal <script> block and a <script setup> block but their lang attributes differ (e.g. <script lang="ts"> with <script setup>). The compiler merges the two and cannot do so safely across language boundaries because babel parser plugins differ per language. Both blocks must share the same language type (js, ts, tsx, jsx) or be unspecified.
Source
Thrown at packages/compiler-sfc/src/compileScript.ts:177
)
content += `\nexport default ${DEFAULT_VAR}`
}
return {
...script,
content,
map,
bindings,
scriptAst: scriptAst.body
}
} catch (e: any) {
// silently fallback if parse fails since user may be using custom
// babel syntax
return script
}
}
if (script && scriptLang !== scriptSetupLang) {
throw new Error(
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
`language type.`
)
}
if (scriptSetupLang && !isTS && scriptSetupLang !== 'jsx') {
// do not process non js/ts script blocks
return scriptSetup
}
// metadata that needs to be returned
const bindingMetadata: BindingMetadata = {}
const helperImports: Set<string> = new Set()
const userImports: Record<string, ImportBinding> = Object.create(null)
const userImportAlias: Record<string, string> = Object.create(null)
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
const setupBindings: Record<string, BindingTypes> = Object.create(null)
View on GitHub (pinned to 9e88707940)
Solutions
- Make the lang attribute identical on both blocks: set both to lang="ts", both to lang="tsx", or remove lang from both.
- If using <script setup> only for setup logic and <script> for side-effect imports, ensure both carry the same lang.
- Remove the normal <script> block if all logic fits in <script setup>.
Example fix
<!-- before -->
<script lang="ts">
import { foo } from './foo'
</script>
<script setup>
const bar = 1
</script>
<!-- after -->
<script lang="ts">
import { foo } from './foo'
</script>
<script setup lang="ts">
const bar = 1
</script> Defensive patterns
Strategy: validation
Validate before calling
function scriptLangsMatch(descriptor: SFCDescriptor): boolean {
const a = descriptor.script?.lang || ''
const b = descriptor.scriptSetup?.lang || ''
return a === b
}
if (descriptor.script && descriptor.scriptSetup && !scriptLangsMatch(descriptor)) {
throw new Error(`Lang mismatch: <script lang="${descriptor.script.lang}"> vs <script setup lang="${descriptor.scriptSetup.lang}">`)
} Type guard
function hasConsistentScriptLang(descriptor: SFCDescriptor): boolean {
if (!descriptor.script || !descriptor.scriptSetup) return true
return (descriptor.script.lang || '') === (descriptor.scriptSetup.lang || '')
} Try / catch
try {
compileScript(descriptor, opts)
} catch (e) {
if (e.message.includes('must have the same language type')) {
// surface to linting / show the user which blocks mismatch
diagnosticReporter.report(descriptor.filename, e.message)
}
throw e
} Prevention
- Add an ESLint rule (e.g. via vue-eslint-parser) that flags mismatched lang attributes on <script> and <script setup>.
- When migrating a <script> to add <script setup>, copy the lang attribute first.
- Codemod tools that split <script> should propagate lang to both resulting blocks.
When it happens
Trigger: An SFC declares <script lang="ts"> and <script setup> (no lang, defaults to js), or vice versa. compileScript reads scriptLang and scriptSetupLang from the descriptor and compares them; any mismatch throws.
Common situations: Developer adds <script setup> to an existing TypeScript SFC but forgets to add lang="ts" to the new block. Migration tooling that splits a single <script> into <script> + <script setup> may drop the lang attribute. Copy-paste between components with inconsistent lang settings.
Related errors
- [@vue/compiler-sfc] SFC contains no <script> tags.
- [@vue/compiler-sfc] ${msg}\n\n${filename}\n${generateCodeFra
- \n\nVue packages version mismatch:\n\n- vue@${vueVersion}\n-
- Invalid server-rendering bundle format. Should be a string o
- \n\nVue packages version mismatch:\n\n- vue@${vueVersion} ($
AI-assisted analysis of vuejs/vue@9e88707940 (2026-08-11).
Data as JSON: /api/errors/d1eaa656e730e0c4.
Report an issue: GitHub.