初めに
Cloudflare Pagesを使って、React (Vite) のアプリをデプロイするまでの手順を備忘録としてまとめました。
ローカル環境にはDockerを使用し、GitHub経由で自動デプロイする構成です!
1. ローカル開発環境の準備(Docker × React × Vite)
my-page-test
├── README.md
├── docker-compose.yml
├── dockerfile
├── index.html
├── node_modules
├── package.json
├── src
│ ├── App.tsx
│ ├── index.css
│ └── main.tsx
└── vite.config.ts
今回は手元の環境を汚さないためにDockerを使います。
dockerfile
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host"]
doker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "5173:5173"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
Dockerfile の罠(exit code: 254)
Alpine Linux(node:20-alpineなど)を使うと、npm install 時に謎の exit code: 254 エラーで落ちることがありました。これは、package.jsonが存在しないと発生する模様。
package.json の準備(Viteのバージョンに注意!)
ReactとViteの環境を作りますが、ここで大きな注意点があります。
Cloudflare Pagesの現在の仕様(2026年時点)では、Viteのバージョンが 6.0.0 以上でないとデプロイ時にエラーで弾かれます!今回は8.0.0にしました!
{
"name": "my-cloudflare-react-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.1",
"typescript": "^5.6.2",
"vite": "^8.0.0"
}
}
vite.config.ts
Dockerコンテナ内からホスト側(手元のブラウザ)へ画面を表示し、ホットリロードを効かせるための設定を追加します。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: true, // コンテナ外からのアクセスを許可
port: 5173, // ポートを5173に固定
watch: {
usePolling: true // Docker環境でも確実にファイル変更を検知させる
}
}
})
そのほか
画面を表示するための最低限のコード(index.html, src/main.tsx, src/App.tsx)を用意します。
ローカルのコンテナで実行してみる
$ docker-compose up
http://localhost:5173/ 等でアクセスできることを確認。
2. GitHubへプッシュ
GitHubのリポジトリにプッシュします。ブランチはmainで構わないです。
3. アカウント作成と「サブドメイン」の変更(超重要!)
Cloudflareのアカウントを作成してPagesを始めると、デフォルトで https://[プロジェクト名].[アカウントのサブドメイン].pages.dev というURLが割り当てられます。
実はこの [アカウントのサブドメイン] 部分に、登録したメールアドレスの@より前の部分などが自動で設定されてしまうことがあります。そのまま公開するとアドレスがバレてしまうので、最初に必ず変更しておきましょう。
-
手順:
- Cloudflareダッシュボードの左メニュー「Workers and Pages」を開く。
- 画面右側(または下部)にある「Account Details」の「subdomain」を探す。
- 「変更」ボタンを押し、メールアドレスとは関係のない好きな文字列(例:
my-sandbox-xyzなど)に変更する。
これだけで、安全なURLで公開できるようになります。
4. Cloudflare Pages へのデプロイ
いよいよCloudflare側でGitHubと連携させます。
ダッシュボードから「Workers と Pages」→「作成」→「Pages」タブを選択。
「Gitプロバイダーに接続」をクリックし、先ほどプッシュしたリポジトリとブランチ(my-page-test)を選択します。
npm error Missing script: "build"が出る場合:
以下のようなメッセージが出ていないか確かめる。
Detected the following tools from environment: bun@1.2.15, nodejs@22.16.0
Cloudflare Pagesのビルド環境には最新の Bun が最初から入っているようです。そのため、package-lock.json がリポジトリに存在しないと、Cloudflareが気を利かせて勝手に bun install を実行してしまいます。
その結果、ビルドコマンドが npm run build のままだと「スクリプトが見つからない(Missing script: "build")」とエラーになります。
解決策は以下のどちらかです:
-
Bunに任せる: 郷に入っては郷に従えで、Cloudflare側のビルドコマンドを bun run build に書き換える。(※今回はこちらで対応しました!)
-
npmを強制する: ローカルで npm install を実行して package-lock.json を生成し、Gitに一緒にプッシュする(これでCloudflareも npm モードになります)。
おわりに
ドメイン周りの仕様や、自動検知によるビルドエラーで少し遠回りしましたが、無事に爆速デプロイ環境を作ることができました!
これからCloudflare Pages + Dockerで環境構築する方の参考になれば幸いです。
おまけ
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Vite React App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
- main.ts
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
- app.tsx
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
return (
<div className="app">
<h1>Welcome to Vite + React</h1>
<p>Edit <code>src/App.tsx</code> and save to test HMR.</p>
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
</div>
)
}
export default App
- index.css
body {
margin: 0;
font-family: system-ui, sans-serif;
background: #f5f5f5;
color: #111827;
}
.app {
max-width: 720px;
margin: 4rem auto;
padding: 2rem;
background: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
text-align: center;
}
button {
border: none;
padding: 0.9rem 1.5rem;
border-radius: 999px;
background: #4f46e5;
color: white;
cursor: pointer;
transition: background 0.2s ease;
}
button:hover {
background: #4338ca;
}
code {
background: rgba(15, 23, 42, 0.08);
padding: 0.15rem 0.4rem;
border-radius: 6px;
}


