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

  1. Make the lang attribute identical on both blocks: set both to lang="ts", both to lang="tsx", or remove lang from both.
  2. If using <script setup> only for setup logic and <script> for side-effect imports, ensure both carry the same lang.
  3. 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

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


AI-assisted analysis of vuejs/vue@9e88707940 (2026-08-11). Data as JSON: /api/errors/d1eaa656e730e0c4. Report an issue: GitHub.