3. Embed Slidev decks
Slidev is a Vue-based slide framework. It is a separate app,
so the approach is: build each deck to a static site under static/, then
embed it with an iframe.
The slides workspace
Decks live in their own npm workspace under slides/, one package per deck.
This lets each deck pin its own Slidev version and dependencies independently.
slides/package.json:
{
"name": "docusaurus-slidev",
"private": true,
"workspaces": ["decks/*"]
}
Write a deck
Each deck is a workspace package at slides/decks/<name>/ with its own
package.json and a slides.md.
slides/decks/intro/package.json:
{
"name": "intro",
"private": true,
"scripts": {
"dev": "slidev --open",
"build": "slidev build",
"export": "slidev export"
},
"devDependencies": {
"@slidev/cli": "^52.0.0",
"@slidev/theme-default": "latest"
}
}
slides/decks/intro/slides.md (slides separated by ---):
---
theme: default
title: Welcome
---
# Slidev in Docusaurus
Press space to advance
---
# A second slide
- Markdown-based
- Embeddable
Install the workspace once from slides/:
npm install
Build script
Docusaurus serves everything in static/ verbatim, so build each deck into
static/slides/<name>/. The site's scripts/build-slidev.mjs:
- discovers each
slides/decks/<name>/slides.md, - runs that deck's own
npm run build(so it uses the deck's pinned Slidev version) with these flags forwarded toslidev build:--out→static/slides/<name>/(where Docusaurus serves it),--base→<baseUrl>/slides/<name>/(so assets resolve on GitHub Pages),--router-mode hash→ avoids server rewrites for a subdirectory deploy, only for Slidev versions that support the flag (older versions error on unknown args, so the script probesbuild --helpfirst),
- and writes
src/slides-manifest.jsonfor the decks index.
The site's slidev:build script simply runs it:
{ "scripts": { "slidev:build": "node scripts/build-slidev.mjs" } }
:::note Mixed Slidev versions
Because each deck pins its own version, one deck can be on Slidev v51 and
another on v52. The build script only passes --router-mode to decks whose
Slidev supports it, so mixed versions build side by side.
:::
The embed component
Create src/components/Slides/index.tsx:
import React from 'react';
import useBaseUrl from '@docusaurus/useBaseUrl';
export default function Slides({ name }: { name: string }) {
const src = useBaseUrl(`/slides/${name}/`);
return (
<iframe
src={src}
title={`Slidev deck: ${name}`}
style={{
width: '100%',
aspectRatio: '16 / 9',
border: '1px solid var(--ifm-color-emphasis-300)',
}}
allow="fullscreen"
loading="lazy"
/>
);
}
Use it in a page
import Slides from '@site/src/components/Slides';
<Slides name="intro" />
Open the deck [full screen](pathname:///slides/intro/).
:::warning Linking to the deck
Use the pathname:// prefix for the full-screen link. A normal link to
/slides/intro/ is a static asset, not a Docusaurus route, so
onBrokenLinks: 'throw' would fail the build.
:::
Hosting multiple decks
Add another workspace package (slides/decks/<name>/ with a package.json and
slides.md), run npm install in slides/, and it builds to /slides/<name>/
automatically. The build script also writes a src/slides-manifest.json
listing each deck's name, title (from frontmatter), and path, which a small
SlidesIndex component renders as a gallery linking to every deck.
Since CI installs the site and the slides workspace separately, remember to run
npm ci in slides/ in the deploy workflow before building the site.