この記事は ひとりCloudflareを使い倒す Advent Calendar 2025 の7日目です
みなさん、LT とか、テックカンファの登壇とか、しますか?
しますよね。
こういうカンファで使うスライドの作成ツールって、何を使いますか…??
私はもっぱら Slidev を使おうと思っているところです。(登壇することがないので…)
[宣伝]
Slidev の日本語版ドキュメントがフル更新されました!(私が頑張りました)
Slidevって英語ドキュメントしかないからなぁ…と思っていた方、是非日本語版で入門してみませんか?
Slidev 日本語版ドキュメント へダイブ!
さて、宣伝もここまでに、Slidev を Workers にホストして、みんながいつでも見られるようにしましょう!
なお、急いで書いているので、中身はスカスカです。リライト要請があったらリライトします。
アーキテクチャ
使うのは
- Cloudflare Workers
- GitHub Actions
- Slidev
だけです!
Workers の解説
今回使うのは、Cloudflare Workers の静的アセット配信です。
Workers は本来 3MB くらいしか載せられません。
しかし、静的アセット (コンパイル・バンドル済みのファイル) を乗せる分には 20,000 ファイルまで、1 ファイル 25MiB まで、乗せることが出来ます。
これを、ふんだんに使います。
とはいえ、静的アセットをホストするだけじゃどうにもなりません。
ルーティングしないと、ってことで Workers をガッツリ書きます。
ルーティング方針
モノレポでやるってことは、1 つのレポジトリに複数のスライドプロジェクトを突っ込むってことです。
なので、ホスティング方針は /slide1/ と /slidev2/ と ... としたいですよね。
できます。大丈夫です。
単純にホスティングディレクトリ直下のディレクトリ構造は全部キープされるので、それぞれのディレクトリに成果物を入れれば ok です。
ってことで、ホスティングしてるディレクトリ直下のアクセス方法を以下のようにして、Workers にルーティングしてもらいます。
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
const path = url.pathname;
// 静的アセット(.html, .css, .js, /assets/)は直接配信
if (
path.endsWith(".html") ||
path.endsWith(".css") ||
path.endsWith(".js") ||
path.endsWith(".json") ||
path.includes("/assets/") ||
path.match(/\.\w+$/) // 拡張子あり
) {
return env.ASSETS.fetch(request);
}
// SPA ルーティング:拡張子なしなら index.html にリライト
const baseMatch = path.match(/^\/([^/]+)/); // ← スライドの base を抽出
if (baseMatch) {
const base = baseMatch[1];
const indexUrl = new URL(`/${base}/index.html`, url.origin);
return env.ASSETS.fetch(indexUrl);
}
// デフォルト
return env.ASSETS.fetch(request);
},
};
あとは、wrangler.jsonc も更新しましょう。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "slidev",
"main": "src/index.ts",
"compatibility_date": "2025-11-23",
"workers_dev": false,
+ "assets": {
+ "directory": "./dist",
+ "binding": "ASSETS",
+ "not_found_handling": "single-page-application"
+ }
}
ビルド方針
Slidev はビルド時に、ベースパスを渡せます。要は、ビルドしたやつは基本的に ./ がベースパスになるので、例えば slide1 の ./slide1/hogehoge.js とかはアクセスできません。(ルートパス直下を指すため、 ./hogehoge.js を読み出してしまう → 結局 ./hoge.js は https://example.com/slide1/hoge.js ではなく、https://example.com/hoge.js をリクエストしてしまうので、動かない)
そこで、ビルド時にベースパスを書き換えます。
import { dirname } from 'node:path'
import { x } from 'tinyexec'
import { execSync } from 'node:child_process'
import fg from 'fast-glob'
import { existsSync } from 'node:fs'
import { cp } from 'node:fs/promises'
const packageFiles = (await fg('slides/!(_template)/package.json', { onlyFiles: true })).sort()
const allSlides = await Promise.all(
packageFiles.map(async (file) => {
const slideRoot = dirname(file)
return { dir: slideRoot }
})
)
const getChangedSlides = () => {
try {
const diff = execSync('git diff --name-only HEAD~1 HEAD', { encoding: 'utf-8' }).toString().trim().split('\n')
const changedSlides = new Set(
diff.filter(file => file.startsWith('slides/')).map(file => file.split('/')[1])
)
return Array.from(changedSlides)
} catch {
return null
}
}
const changedSlides = getChangedSlides()
const slides = allSlides.filter(s => {
const distStalePath = `dist-stale/${s.dir.replace('slides/', '')}`
if (changedSlides) {
return changedSlides.includes(s.dir.replace('slides/', ''))
}
if (!existsSync(distStalePath)) return true;
return false;
})
await Promise.all(
slides.map(async (slide) => {
console.log(`Building slide in ${slide.dir}...`);
const buildCommand = [
"slidev",
"build",
"--base",
`/${slide.dir.replace("slides/", "")}/`,
"--out",
`../../dist-stale/${slide.dir.replace("slides/", "")}`,
];
await x("pnpm", [...buildCommand], {
nodeOptions: { cwd: slide.dir, stdio: "inherit" },
});
})
)
await cp('dist-stale/', 'dist/', { recursive: true })
以上のコードでは、キャッシュ戦略とかいろんなことをやっていますが、説明は端折ります。許してください…
で、ビルドコマンドを書きます。
{
"scripts": {
// ...
+ "build": "rimraf dist && tsx scripts/build.ts"
// ...
}
}
GitHub Actions を使ったデプロイ
name: Deploy Slides
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy Slidev Slides to Cloudflare Workers
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install dependencies
run: npm install -g pnpm && pnpm install
- name: Build Slidev Slides
run: pnpm build
- name: Publish to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy
- name: Post Deployment Message
run: echo "Slidev slides have been successfully deployed to Cloudflare Workers!"
これを実行するうえで、GitHub Actions の Secret を設定してください。→ 詳しくはこちら
できあがり
端折りすぎましたが、Done is better than perfect を都合良く解釈しましょう。
とにかく説明したかったのは
- ルーティング方針
- ビルド方針
です!
この 2 つがわかっていれば、Slidev をデプロイできます!
ビバ Slidev ライフ!
参考資料