元々はCloudflare Pagesを使ってSPAを公開する手順のメモを書くつもりでしたが、本記事を書いている時点ではCloudflare Workersの利用が推奨されていますので、Cloudflare WorkersでSPAを公開する手順のメモを書き残しておきます。制限の範囲内であれば、Cloudflare Workersは無料プランから始めることができます。
C3を使ってWorkerプロジェクトを新規作成する
公開中のSPA(筋トレタイマー)をCloudflare Workersに移行してみます。
まずは、CLIのC3(create-cloudflare-cli)を使ってWorker用のプロジェクトを作ります。プロジェクトの名前はmtt-appとします。
cd <作業フォルダー>
npm create cloudflare@latest -- mtt-app
筋トレタイマーはVueとViteを使って開発しましたので、選択肢からFramework Starterを選びます。

フレームワークの一覧からVueを選びます。
Vueを選ぶとTypeScriptを使うか否(JavaScript)か尋ねられます。筋トレタイマーはTypeScriptを使って開発しましたので、TypeScriptを選びます。
TypeScriptを使ったVueのプロジェクトが作成されます。

バージョン管理にgitを使うか否か尋ねられるので、yesを選びます。

最後にアプリをデプロイするか否か尋ねられますが、ここはNoを選んでデプロイは後で行うことにします。
これでVueとViteを使ったWorkerプロジェクトの準備が整いました。Visual Studio CodeやCursorなどのエディターを使ってプロジェクトを開きます。
cursor mtt-app # Visual Studio Codeの場合はcode、Antigravityの場合はagyエディターのターミナルでnpm run devを実行すると、テンプレートのVueアプリが動きます。
Workerプロジェクトに既存のSPAを移植する
ls -aFコマンドでWorkerプロジェクトのファイルを確認します。
./ env.d.ts README.md tsconfig.worker.json
../ index.html server/ vite.config.ts
.git/ node_modules/ src/ worker-configuration.d.ts
.gitignore package-lock.json tsconfig.app.json wrangler.jsonc
.vscode/ package.json tsconfig.json
.wrangler/ public/ tsconfig.node.json.wranglerディレクトリは.gitignoreに追加されているので無視します。ViteのCLIで作成した場合と比べると以下のファイルが追加されていますが、これらのファイルにはCloudflare Workersでバックエンド(/api/)を実装するために必要なコードが書かれていますので、今回はこのままにしておきます。
server/index.tstsconfig.worker.jsonworker-configuration.d.tswrangler.jsonc
他は見覚えのあるファイルばかりですので、筋トレタイマーのソースコードを移植していきます。
# packages.jsonに必要なパッケージを追加
npm i -D @tailwindcss/vite eslint eslint-plugin-vue prettier prettier-plugin-tailwindcss tailwindcss vue-component-type-helpersvite.config.tsの内容を変更します。TailwindCSSのプラグイン、環境変数(.env)の読み込み、base・server・buildの設定を追加しています。
vite.config.tsの詳細
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import { cloudflare } from "@cloudflare/vite-plugin"
// 追加
import tailwindcss from "@tailwindcss/vite"
import { loadEnv } from 'vite'
// https://vite.dev/config/
export default defineConfig(({mode}) => {
const env = loadEnv(mode, process.cwd(), "")
return {
plugins: [
vue(),
vueDevTools(),
cloudflare(),
tailwindcss(), // 追加
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
// 以下、追加
// NOTE: Vue RouterのcreateWebHistory(import.meta.env.BASE_URL)で用いられるベースURLは`base`で設定する。
base: env.BASE_URL || "/",
server: {
host: true,
hmr: {
host: "localhost",
// HMR crashes with ENOENT when deleting SVG asset with Vite + Tailwind v4
// https://github.com/vitejs/vite/issues/19786
overlay: false,
},
},
build: {
outDir: fileURLToPath(
new URL(env.BUILD_DIR || "./dist", import.meta.url)
),
emptyOutDir: true, // ビルド時にフォルダーを空にする(以前のjsファイルなどが残るため)
chunkSizeWarningLimit: 1024 * 1024 * 10, // 10MiB
},
}
})tsconfig.app.jsonのcompilerOptionsに筋トレタイマーの設定をコピーします。tsconfig.node.jsonはデフォルトのままにします。
tsconfig.app.jsonの詳細
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
},
// 以下、追加
"target": "ES2024",
"lib": ["ES2024", "DOM"],
"module": "ESNext",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
}
}.prettierrc、.prettierignore、.editorconfig、.markdownlint.jsonは筋トレタイマーのソースコードからコピーします。内容は古くなっているかもしれないので、ここでは省略します😅
最後にindex.html、public/フォルダー、src/フォルダーを筋トレタイマーのプロジェクトからコピーして、ビルドを試してみます。
npm run buildエラーが3つ見つかったので、これらを修正して再度ビルドを行います。
ビルドエラーが全て解決したら、ローカルで動かしてみます。
npm run dev
以下のメッセージが表示されていますが、筋トレタイマーは正常に動作できました。
The latest compatibility date supported by the installed Cloudflare Workers Runtime is "2025-12-02",
but you've requested "2025-12-09". Falling back to "2025-12-02"...この変更をcommitして、GitHubにリポジトリを作成・pushします。
Cloudflareのダッシュボードでアプリを作成する
Cloudfrareにログインしてダッシュボード画面を表示します。ビルド→コンピューティングとAI→Workers & Pagesを開いて、アプリケーションを作成するボタンをクリックすると、Workerの作成画面が表示されます。

話は逸れますが、C3を使ってPagesプロジェクトを作成した場合、下にあるGet startedをクリックしてアプリケーションの作成作業を進めることができます。
初めてContinue with GitHubをクリックした場合、Install & Authorize Cloudflare Workers and Pagesという画面が表示されます。これはCloudflareではなくGitHub.comの画面であり、GitHubアカウントのSettings→Applicationsの画面で設定を変更・削除できます。

Only select repositoriesを選んで筋トレタイマーのリポジトリを追加し、Install & Authorizeボタンをクリックします。

Workerの画面に戻って、GitHubのリポジトリを選びます。
デプロイするアプリケーションの設定画面が表示されます。

ここはデフォルトのままデプロイボタンをクリックすると、GitHubからリポジトリをクローンして、ビルドとデプロイが走り始めます。

最後までエラーが発生すること無く、無事にビルドとデプロイができました。

訪問するボタンをクリックすると、筋トレタイマーが無事起動できました!

エディターでソースコードを修正してcommit・pushすると、Cloudflareで自動的にビルドが動いて、新しいバージョンのアプリが自動的にデプロイされます。
Cloudflare Workersは簡単にSPAをデプロイできます
ところで筋トレタイマーはVue Routerを使っており、/以外のパス(例えば/training/draw-in)でページを切り替えるように作ってあります。これをApacheで公開するときは.htaccessを作ってindex.htmlにrewriteする必要がありました。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>しかし、Cloudflare Workersのデプロイ作業ではそのような設定は行っていません。どうやっているのか不思議に思って調べてみたところ、Routing behaviorというドキュメントに説明がありました。
最初にC3を使ってWorkerプロジェクトを作成したときに作られていたwrangler.jsoncに以下のような設定が書かれており、これがindex.htmlへ誘導するための設定になります。
{
中略
"assets": {
"not_found_handling": "single-page-application"
},
中略
}
not_found_handling = "single-page-application": Sets your application to return a200 OKresponse withindex.htmlfor requests which don’t match a static asset. Use this if you have a Single Page Application. We recommend pairing this with selective routing usingrun_worker_firstfor advanced routing control.
wrangler.jsoncの詳細な説明はCloudflareのドキュメントに記載されていますが、サービスの各種設定がこのファイル1つでできるようになっているのはとても便利です。何よりC3を使ってVue用のWorkerプロジェクトを作るだけで自動的に設定されることが嬉しいですね✨
Cloudflare Workersの無料プランで自作のSPAを簡単に公開することができました。独自ドメインを取得してカスタムドメインを設定すれば、このまま公開しても良さそうです。(そんなにアクセス数は多くないはずなので😅)
レンタルサーバを使わなくてもCloudflare Workersを使ってフルスタックアプリケーションを無料プランで公開できるのはありがたいです。次回はバックエンド(/api/)を実装して、フルスタックアプリケーションをWorkerで動かしてみたいと思います。
