slidevjs/slidev · critical · Error

[Slidev] Internal error: <script setup> block not found in s

Error message

[Slidev] Internal error: <script setup> block not found in slide ${index + 1}.

What it means

This is an internal error thrown by Slidev's `slidev:layout-wrapper` Vite plugin when transforming a compiled markdown slide. After resolving the layout, the plugin regex-matches `/^<script setup.*>/m` (RE_SCRIPT_SETUP_TAG) against the slide's transformed code; every slide is expected to already contain a `<script setup>` block injected by an upstream pipeline stage. The throw fires only when that contract is broken — the slide reached the layout wrapper without a script-setup block, so the plugin cannot safely splice in the layout import and context setup it appends.

Source

Thrown at packages/slidev/node/vite/layoutWrapper.ts:40

        if (!match)
          return
        const [, no, type] = match
        if (type !== 'md')
          return
        const index = +no - 1
        const layouts = await utils.getLayouts()
        const rawLayoutName = data.slides[index]?.frontmatter?.layout ?? data.slides[0]?.frontmatter?.defaults?.layout
        let layoutName = rawLayoutName || (index === 0 ? 'cover' : 'default')
        if (!layouts[layoutName]) {
          console.error(red(`\nUnknown layout "${bold(layoutName)}".${yellow(' Available layouts are:')}`)
            + Object.keys(layouts).map((i, idx) => (idx % 3 === 0 ? '\n    ' : '') + gray(i.padEnd(15, ' '))).join('  '))
          console.error()
          layoutName = 'default'
        }

        const setupTag = code.match(RE_SCRIPT_SETUP_TAG)
        if (!setupTag)
          throw new Error(`[Slidev] Internal error: <script setup> block not found in slide ${index + 1}.`)

        const templatePart = code.slice(0, setupTag.index!)
        const scriptPart = code.slice(setupTag.index!)

        const bodyStart = templatePart.indexOf('<template>') + 10
        const bodyEnd = templatePart.lastIndexOf('</template>')
        let body = code.slice(bodyStart, bodyEnd).trim()
        if (body.startsWith('<div>') && body.endsWith('</div>'))
          body = body.slice(5, -6)

        return [
          templatePart.slice(0, bodyStart),
          `<InjectedLayout v-bind="_frontmatterToProps($frontmatter,${index})">\n${body}\n</InjectedLayout>`,
          templatePart.slice(bodyEnd),
          scriptPart.slice(0, setupTag[0].length),
          `import InjectedLayout from "${toAtFS(layouts[layoutName])}"`,
          templateImportContextUtils,
          templateInitContext,

View on GitHub (pinned to 0d798ace58)

Solutions

  1. This is an internal invariant — file a Slidev bug report with the slide content and the offending slide number from the message.
  2. Verify all @slidev/* packages resolve to the same version (run the version check / dedupe node_modules); a mixed-version install is the most common root cause.
  3. Audit any custom Vite plugins you added via `vitePlugins` or Slidev options for a `transform` hook that handles `*.md` ids and may strip or replace the `<script setup>` block before the layout-wrapper plugin runs.
  4. Disable third-party Vite/Vue plugins one at a time to isolate which transform removes the script-setup tag.
  5. Clear Vite's cache (`node_modules/.vite`) and restart the dev server to rule out a stale transform cache.

Example fix

// before: a custom plugin transforming md and dropping the script tag
transform(code, id) {
  if (id.endsWith('.md')) return rewriteHtmlOnly(code) // drops <script setup>
}
// after: bail out for slide source ids so Slidev's pipeline owns them
import { regexSlideSourceId } from '@slidev/node/vite/common'
transform(code, id) {
  if (regexSlideSourceId.test(id)) return // let Slidev handle slide modules
  if (id.endsWith('.md')) return rewriteHtmlOnly(code)
}
Defensive patterns

Strategy: validation

Validate before calling

// If you author custom Vite plugins, never strip the script-setup tag from
// slide source ids. Bail out for ids Slidev owns:
import { regexSlideSourceId } from '@slidev/node/vite/common'
function ownsSlide(id: string): boolean {
  return regexSlideSourceId.test(id)
}
// in your transform: if (ownsSlide(id)) return undefined

Type guard

// Guard the invariant the plugin relies on, for tests/diagnostics:
const RE_SCRIPT_SETUP_TAG = /^<script setup.*>/m
function hasScriptSetup(code: string): boolean {
  return RE_SCRIPT_SETUP_TAG.test(code)
}

Prevention

When it happens

Trigger: Fires inside the plugin's `transform.handler` for any module id matching `regexSlideSourceId` with type `md`. Concretely: `code.match(RE_SCRIPT_SETUP_TAG)` returns null. This happens when an earlier transform in the pipeline that is supposed to prepend the `<script setup>` block did not run, ran out of order, or was overridden by a custom user Vite plugin that rewrites/strips the slide module output before the layout wrapper sees it.

Common situations: Mismatched @slidev package versions (e.g. @slidev/client or @slidev/parser newer/older than @slidev/parser that emits the script setup), a custom Vite plugin in `vitePlugins`/`unocss` config that transforms `*.md` ids and clobbers the `<script setup>` tag, or a fork/patch of Slidev whose markdown-to-SFC compiler no longer emits the tag. The error names the offending slide by 1-based index (`index + 1`).

Related errors


AI-assisted analysis of slidevjs/slidev@0d798ace58 (2026-08-12). Data as JSON: /api/errors/4a5d319bd46d3372. Report an issue: GitHub.