vuejs/core · 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 by compileScript in @vue/compiler-sfc when an SFC declares BOTH a normal <script> block and a <script setup> block but their lang attributes differ. Vue merges the two blocks into one module, so they must compile under the same language pipeline. The check at compileScript.ts:189 compares script.lang against scriptSetup.lang and aborts before any transformation runs.

Source

Thrown at packages/compiler-sfc/src/compileScript.ts:189

): SFCScriptBlock {
  if (!options.id) {
    warnOnce(
      `compileScript now requires passing the \`id\` option.\n` +
        `Upgrade your vite or vue-loader version for compatibility with ` +
        `the latest experimental proposals.`,
    )
  }

  const { script, scriptSetup, source, filename } = sfc
  const hoistStatic = options.hoistStatic !== false && !script
  const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
  const scriptLang = script && script.lang
  const scriptSetupLang = scriptSetup && scriptSetup.lang
  const isJSOrTS =
    isJS(scriptLang, scriptSetupLang) || isTS(scriptLang, scriptSetupLang)

  if (script && scriptSetup && scriptLang !== scriptSetupLang) {
    throw new Error(
      `[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
        `language type.`,
    )
  }

  if (!scriptSetup) {
    if (!script) {
      throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
    }

    // normal <script> only
    if (script.lang && !isJSOrTS) {
      // do not process non js/ts script blocks
      return script
    }

    const ctx = new ScriptCompileContext(sfc, options)
    return processNormalScript(ctx, scopeId)

View on GitHub (pinned to a2b40db9a8)

Solutions

  1. Make both <script> and <script setup> use the same lang attribute (usually both lang="ts").
  2. If the normal <script> only exists for side-effect imports or options that belong in setup, move that content into <script setup> and remove the duplicate block.
  3. If you genuinely need two languages, split the logic into a separate imported .ts/.js module instead of a second SFC script block.

Example fix

// before
<script lang="ts">
export default { inheritAttrs: false }
</script>
<script setup lang="tsx">
// ...
</script>

// after
<script lang="ts">
export default { inheritAttrs: false }
</script>
<script setup lang="ts">
// ...
</script>
Defensive patterns

Strategy: validation

Validate before calling

// Before compileScript, verify both script blocks share a lang.
function canCompileScript(sfc: { script?: { lang?: string }; scriptSetup?: { lang?: string } }) {
  if (sfc.script && sfc.scriptSetup) {
    const a = sfc.script.lang || ''
    const b = sfc.scriptSetup.lang || ''
    if (a !== b) return false
  }
  return true
}

Type guard

function scriptLangsMatch(sfc: { script?: { lang?: string }; scriptSetup?: { lang?: string } }): boolean {
  if (!sfc.script || !sfc.scriptSetup) return true
  return (sfc.script.lang || '') === (sfc.scriptSetup.lang || '')
}

Prevention

When it happens

Trigger: An .vue file containing e.g. <script lang="ts"> and <script setup lang="tsx">, or <script> (no lang, JS) next to <script setup lang="ts">. Calling compileScript(sfc, options) on such an SFC reproduces it.

Common situations: Mixing a TypeScript <script setup> with a plain JS <script> for non-setup exports; copy-pasting a script block from another SFC with a different lang; tooling that injects a <script> block with a default lang.

Related errors


AI-assisted analysis of vuejs/core@a2b40db9a8 (2026-08-12). Data as JSON: /api/errors/f9871382e95e5a49. Report an issue: GitHub.