Múltiplos decks Slidev em um repositório
Este guia mostra como manter vários decks do Slidev em um único repositório, publicá-los no GitHub Pages em subcaminhos independentes e embutir a URL já publicada em uma página do Docusaurus.
Como funciona
O Slidev builda um deck por vez. Para ter vários no mesmo repositório, cada
deck é buildado com seu próprio base path (--base) e sua própria pasta de saída
(--out), e tudo é reunido em um único diretório dist/. O resultado publicado
fica assim:
https://<usuario>.github.io/<repo>/ → índice com links
https://<usuario>.github.io/<repo>/slide01/ → deck 1
https://<usuario>.github.io/<repo>/slide02/ → deck 2
Estrutura do repositório
<repo>/
├── decks/
│ ├── slide01/slides.md
│ └── slide02/slides.md
├── build.mjs # builda todos os decks + gera o índice
├── package.json
└── .github/workflows/deploy.yml
Cada deck é apenas uma pasta com um slides.md. Para adicionar um novo deck,
basta criar decks/slide03/slides.md — nada mais precisa ser editado.
O nome da pasta do deck vira o subcaminho na URL. Prefira nomes descritivos
(introducao, protocolos) a números — a URL fica mais legível.
Inicialização do repositório
Crie um novo repositório no GitHub, adicione os arquivos e rode os comandos abaixo para inicializar o projeto:
pnpm install
git init && git add . && git commit -m "Setup multi-deck Slidev"
git branch -M main
git remote add origin https://<usuario>.github.io/<repo>/.git
git push -u origin main
Configuração de cada deck
No headmatter (primeiro bloco ---) de cada slides.md, use roteamento por
hash. Isso evita erro 404 ao atualizar (F5) em um slide interno e é essencial
quando o deck for embutido em um iframe.
---
routerMode: hash
theme: seriph
title: Introdução ao Slidev
---
# Primeiro slide
Com roteamento por hash a URL do slide fica após o #
(.../slide01/#/3). Tudo depois do # não é enviado ao servidor, então o
GitHub Pages sempre entrega o index.html do deck e o slide correto é
resolvido no navegador. Sem isso, atualizar em um slide interno retorna 404.
Script de build
O build.mjs detecta cada decks/*/slides.md, builda com o base path correto e
gera uma página índice em dist/index.html. Em CI, o nome do repositório vem da
variável GITHUB_REPOSITORY, então o base path se ajusta automaticamente.
import { execSync } from "node:child_process";
import {
readdirSync,
rmSync,
writeFileSync,
existsSync,
statSync,
} from "node:fs";
import { join, resolve } from "node:path";
const REPO = process.env.GITHUB_REPOSITORY?.split("/")[1] || "iiot-slides";
const DECKS_DIR = "decks";
const OUT_DIR = "dist";
if (existsSync(OUT_DIR)) rmSync(OUT_DIR, { recursive: true, force: true });
const decks = readdirSync(DECKS_DIR).filter((name) => {
const p = join(DECKS_DIR, name);
return statSync(p).isDirectory() && existsSync(join(p, "slides.md"));
});
for (const deck of decks) {
const entry = join(DECKS_DIR, deck, "slides.md");
const base = `/${REPO}/${deck}/`;
const out = resolve(OUT_DIR, deck); // absoluto: slidev resolve --out relativo ao slides.md
execSync(`npx slidev build ${entry} --base ${base} --out ${out}`, {
stdio: "inherit",
});
}
const links = decks
.map((d) => ` <li><a href="./${d}/">${d}</a></li>`)
.join("\n");
writeFileSync(
join(OUT_DIR, "index.html"),
`<!doctype html>
<html lang="pt-BR"><head><meta charset="utf-8"><title>Slides</title></head>
<body><h1>Apresentações</h1><ul>\n${links}\n</ul></body></html>\n`,
);
O --out do Slidev é resolvido relativo ao slides.md, não à raiz do
projeto. Por isso o script usa um caminho absoluto (resolve(...)) — passar
dist/slide01 direto faria a saída ir parar em decks/slide01/dist/.
Publicação no GitHub Pages
Use o workflow oficial do Slidev adaptado para rodar o build.mjs:
name: Deploy pages
on:
workflow_dispatch: {}
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
- run: npm i -g @antfu/ni
- run: nci
- name: Build all decks
run: node build.mjs
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
steps:
- id: deployment
uses: actions/deploy-pages@v4
Antes do primeiro deploy, vá em Settings → Pages → Source e selecione
GitHub Actions. Não use enablement: true no passo configure-pages — o
token do workflow normalmente não tem permissão para criar o site, o que causa
o erro Resource not accessible by integration.
Embutir a URL já publicada
Com os decks no ar, embuta qualquer um em uma página .mdx do Docusaurus usando
um iframe. Em MDX o style é um objeto JavaScript:
<iframe
src="https://<usuario>.github.io/<repo>/slide01/"
style={{ width: "100%", aspectRatio: "16 / 9", border: 0, borderRadius: 8 }}
allowFullScreen
/>
Para abrir em um slide específico, aproveite o roteamento por hash:
<iframe
src="https://<usuario>.github.io/<repo>/slide02/#/3"
style={{ width: "100%", aspectRatio: "16 / 9", border: 0, borderRadius: 8 }}
allowFullScreen
/>
Componente reutilizável (opcional)
Para não repetir o iframe, crie um componente em
src/components/SlideEmbed.jsx:
import React from "react";
const BASE = "https://<usuario>.github.io/<repo>";
export default function SlideEmbed({ deck, slide = 1, aspect = "16 / 9" }) {
const src = `${BASE}/${deck}/#/${slide}`;
return (
<div style={{ margin: "1.5rem 0" }}>
<iframe
src={src}
title={`Slides: ${deck}`}
loading="lazy"
allowFullScreen
style={{
width: "100%",
aspectRatio: aspect,
border: "1px solid var(--ifm-color-emphasis-300)",
borderRadius: 8,
}}
/>
<p>
<a href={src} target="_blank" rel="noreferrer">
Abrir em nova aba ↗
</a>
</p>
</div>
);
}
E use em qualquer .mdx:
import SlideEmbed from "@site/src/components/SlideEmbed";
<SlideEmbed deck="slide01" />
<SlideEmbed deck="slide02" slide={3} />
Para apenas linkar um deck (sem embutir), use um link normal:
[Ver apresentação](https://<usuario>.github.io/<repo>/slide01/).