synon.co.jp を STUDIO から Cloudflare Workers へ移行する
+ GitHub Actions による push 自動デプロイ構築手順書
実測した全20ページを、素の HTML/CSS/JS で作り直し、GitHub への push で自動デプロイする。画面操作・設定ファイル・ワークフロー YAML まで具体的に記載した実務文書。
作成日: 2026-08-19
移行元: STUDIO
移行先: Cloudflare Workers (Static Assets)
実装: 素の HTML/CSS/JS
CI/CD: GitHub Actions
費用: ¥0 / 月
目次
エグゼクティブサマリ
実測した現状
移行先の選定 — Workers を選ぶ理由
前提条件 — 先に DNS を移管する
リポジトリとサイトの再構築
Cloudflare Workers の設定
GitHub Actions で自動デプロイ
DNS 切替とロールバック
SEO・既存URL維持・リダイレクト
既存 Cloudflare 資料との整合・訂正
未確定事項
まとめ
0 エグゼクティブサマリ
結論を3行で
移行対象は全20ページ。実測で確定しました 素の HTML/CSS/JS で作り直す判断は、この規模なら妥当です。
最大の前提条件は「先に DNS を Cloudflare へ移管すること」 Workers のカスタムドメインは Cloudflare DNS 上のゾーンでしか設定できません。 現在 synon.co.jp の NS は Xserver のままなので、既存資料の「① DNS移管」を先に完了させないと、この移行は着手できません。
「WebMCP に対応しているから Workers」という理由付けは、技術的には成立しません WebMCP はブラウザ内で完結するクライアントサイド仕様で、ホスティング先を選びません。ただし結論(Workers を選ぶ)は正しい ので、理由を言い直せば済みます(2章)。
実測した現状(2026-08-19 時点)
synon.co.jp NS ns1〜ns5.xserver.jp ← Cloudflare ではない(前提条件に直結)
synon.co.jp A 203.0.113.10 ← STUDIO(Google Cloud の共有IP)
*.synon.co.jp A 203.0.113.10 ← ワイルドカードが存在
www A 198.51.100.10 ← Xserver。apex と別基盤を指している
synon.co.jp MX Google Workspace(5レコード)
CAA / AAAA / DS なし
〜¥65,000
年間の削減額(STUDIO のプラン次第)
区分 件数 URL
静的ページ 16 / /about /service /service-1 /development /development-1 /works-system /work-app /work-1 /company-org /company-1 /topics /contact /privacy /termsofuse /1
CMS: topics 2 /topics/dify(2025-01-11) /topics/info(2025-01-10)
CMS: posts 2 /posts/zsFXXiMM(2026-01-07) /posts/renew(2025-01-10)
実測して気づいた4つのこと
# 気づき 意味
1 連番URLが5つある (/1 /service-1 /development-1 /work-1 /company-1)STUDIO でページを複製した痕跡と考えられます。このまま20ページ全部を作り直すのではなく、まず「本当に必要なページか」を棚卸ししてください。 重複コンテンツは SEO 上もマイナスです(4.3節)
2 STUDIO CMS を2種類使っている (topics と posts)「CMS 不要」という方針はコンテンツが4件しかないので成立します。 ただし今後お知らせを追加するたびに開発者が HTML を書く 運用になります。この点だけは合意を取っておいてください(4.5節)
3 www が apex と別のサーバー(Xserver)を指している既存資料で「ダングリングの可能性」として挙げた項目が確定しました。 本移行はこれを直す好機です(9.2節)
4 ワイルドカード * が存在する 存在しないサブドメインもすべて STUDIO の IP を返します。移行時に明示レコードへ置き換える 判断ができます(8.3節)
この移行で同時に解決すること
着手前に必ず理解しておくこと
# 事実 出典
1 🔴 STUDIO は HTML/CSS/JS を書き出せません。 公式ヘルプに「サイトのHTMLコードを外部へエクスポートする機能は提供されていません」と明記。有料プランでも不可。 つまり移行は「作り直し」です help.studio.design
2 🔴 STUDIO CMS のデータもエクスポートできません。 本件は4件なので手作業で問題ありません 同上
3 🔴 STUDIO を Free プランへ落とすと、その瞬間にサイトが非公開になります。 「公開中のサイトは自動的に非公開になる」と公式に明記。解約は必ず新サイトの稼働を確認してから (7.4節) 同上
4 🔴 Workers のカスタムドメインは Cloudflare DNS 上のゾーンが必須です。 "You must have an active Cloudflare zone"。DNS 移管が先です (3章) developers.cloudflare.com
5 ⚠️ GitHub Actions から Cloudflare への OIDC 認証は現時点で未対応です。 長期有効な API トークンを GitHub Secrets に置く方式しかありません。緩和策は 6.6節 同上 / GitHub Discussions
やってはいけないこと
# 禁止事項 起きること
1 🔴 DNS 移管より先に Workers のカスタムドメインを設定しようとする そもそも設定画面で選択できません。作業順序が破綻します
2 🔴 新サイトの動作確認前に STUDIO を解約・ダウングレードする その瞬間にサイトが落ちます。 切り戻し先が消えます
3 🔴 pull_request_target でプレビューをデプロイする フォークからの PR で Cloudflare API トークンを盗まれます (Pwn Request)
4 🔴 API トークンに「Edit Cloudflare Workers」テンプレートをそのまま使う KV / R2 / Routes まで含む広い権限になります。Custom Token で最小権限を切ってください (6.2節)
5 ⚠️ 20ページを何も考えず1:1で作り直す 連番URLの重複ページまで移植することになります。先に棚卸し (4.3節)
6 ⚠️ /posts/zsFXXiMM のような自動生成スラッグを新サイトで再現しない 被リンク・インデックスが切れます。維持するか 301 するかを明示的に決める (8章)
1 実測した現状
1.1 DNS レコード
$ dig +short NS synon.co.jp
ns1.xserver.jp. ns2.xserver.jp. ns3.xserver.jp. ns4.xserver.jp. ns5.xserver.jp.
$ dig +short A synon.co.jp
203.0.113.10 # STUDIO(Google Cloud の共有IP)
$ dig +short A www.synon.co.jp
198.51.100.10 # Xserver ← apex と別基盤
$ dig +short A zzq7x9-none.synon.co.jp
203.0.113.10 # ワイルドカードが効いている
$ dig +short MX synon.co.jp
1 ASPMX.L.GOOGLE.COM.
5 ALT1.ASPMX.L.GOOGLE.COM. 5 ALT2.ASPMX.L.GOOGLE.COM.
10 ALT3.ASPMX.L.GOOGLE.COM. 10 ALT4.ASPMX.L.GOOGLE.COM.
$ dig +short CAA synon.co.jp # なし
$ dig +short AAAA synon.co.jp # なし
203.0.113.10 について
この IP は STUDIO 公式ヘルプが全ユーザー共通で案内している値 です。つまり専用IPではなく共有IP で、Google Cloud のロードバランサと考えられます。STUDIO は CNAME をサポートしていない ため、apex も www も A レコードで指定する仕様です。
メールへの影響がないことの確認
synon.co.jp syncsync.jp(比較)
MX の指す先 Google Workspace の5ホスト syncsync.jp(apex 自身)
apex を切り替えた場合 ✅ 影響なし 🔴 受信メール全滅
syncsync.jp 版で最大の地雷だった「MX が apex を指している」問題が、synon.co.jp には存在しません。 apex を安全に Cloudflare へ向けられます。
1.2 ページ構成(全20URL)
sitemap.xml
├── sitemap-static.xml … 16件
├── sitemap-dynamic/sitemap-dynamic-topics-s--c-slug.xml … 2件
└── sitemap-dynamic/sitemap-dynamic-posts-s--c-slug.xml … 2件
# URL 推定される内容 移行判断
1 /トップ 必須
2 /about会社について 必須
3 /company-org組織図 必須
4 /company-1? 🔍 要確認 (/about と重複していないか)
5 /serviceサービス一覧 必須
6 /service-1? 🔍 要確認
7 /development開発について 必須
8 /development-1? 🔍 要確認
9 /works-system実績(システム) 必須
10 /work-app実績(アプリ) 必須
11 /work-1? 🔍 要確認
12 /topicsお知らせ一覧 必須
13 /contact問い合わせフォーム 必須(フォームの代替が必要 — 4.7節)
14 /privacyプライバシーポリシー 必須
15 /termsofuse利用規約 必須
16 /1? 🔍 要確認 (テストページの可能性)
URL lastmod コレクション
/topics/dify2025-01-11 topics
/topics/info2025-01-10 topics
/posts/zsFXXiMM2026-01-07 posts
/posts/renew2025-01-10 posts
2つの注意点
/posts/zsFXXiMM はランダム文字列のスラッグ です。STUDIO が自動採番したものと考えられます。新サイトで意味のある URL に変えたい場合は、必ず旧URLから 301 を張ってください (8.1節)
topics と posts が別コレクションとして併存しています。 役割の違いを確認し、新サイトでは1本化するのが自然です。 その場合も旧URLからの 301 が必要です
1.3 サイトの実装状況
項目 実測値
<meta name="generator">STUDIO (STUDIO 製であることが確定)
description「ビジネスにおける時短サービスを提供」
og:imagehttps://storage.googleapis.com/production-os-assets/assets/460ce977-... ← STUDIO のストレージ。移行時に自社側へ移す必要あり
og:type / twitter:cardwebsite / summary_large_image
robotsall
構造化データ(JSON-LD) 確認できず (=新サイトで新規実装する余地がある)
robots.txtUser-agent: * / Allow: / / Sitemap: https://synon.co.jp/sitemap.xml
OGP 画像が STUDIO のストレージにあります
STUDIO を解約するとこの画像が消える可能性があります。 移行前に全画像アセットをダウンロードして自社リポジトリに取り込んでください (4.2節)。
取得できたのは HTML の骨格のみです
本文は JavaScript で描画されている可能性があります。ページ内容の正確な把握は、ブラウザで1ページずつ開いて行ってください。
2 移行先の選定 — Workers を選ぶ理由を正しく言い直す
2.1 まず訂正:WebMCP はホスティング先を選びません
「サイトが WebMCP に対応しているので Workers にしたい」 という理由付けは、技術的には成立しません。 先に事実を確認します。
WebMCP 仕様の原文(W3C Web Machine Learning Community Group / Draft Community Group Report)
"Web pages that use WebMCP can be thought of as Model Context Protocol servers that implement tools
in client-side script instead of on the backend ."
技術ノートにも "running entirely client-side in the browser" 、バックエンド実行は "out of scope" と明記されています。
社内説明としては訂正が必要です
「WebMCP 対応のために Workers が必要」と説明したまま進めると、後から「なぜ Workers なのか」を説明できなくなります。
2.2 それでも Workers が正解である3つの理由
結論(Workers を選ぶ)は変えなくて構いません。 理由を差し替えれば済みます。
稟議・社内説明にはこう書いてください
「Cloudflare が新規プロジェクトに推奨しているのが Workers であること、および将来 synon.co.jp/mcp にリモート MCP サーバーを同居させる構想があることから、Workers(Static Assets)を採用する。WebMCP(ブラウザ内でツールを公開する仕様)の実装自体はホスティング先を問わないが、Workers なら同じリポジトリ・同じデプロイ経路でサーバー側 MCP まで一体運用できる。」
2.3 Workers(Static Assets)と Pages の比較
項目 Workers(Static Assets) Pages
Cloudflare の推奨 ✅ 新規はこちら 継続サポートのみ
今後の機能開発 ✅ すべてここに集約 ❌ 実質停止
静的アセットのリクエスト課金 無料・無制限 無料・無制限
ファイル数上限(Free) 20,000ファイル / バージョン 20,000ファイル
1ファイルの最大サイズ 25 MiB 25 MiB
_headers / _redirects✅ ネイティブ対応 ✅ 対応
Durable Objects ✅ 同一 Worker 内で定義可 ⚠️ 別 Worker を作ってバインドが必要
Cron Triggers ✅ ❌
Workers Logs / Tail Workers ✅ ❌
Source Maps ✅ ❌
Git 組み込み連携 ✅ Workers Builds ✅
Early Hints ❌ ✅
Cloudflare ゾーン外のカスタムドメイン ❌ ✅
Pages にしかない機能で本件に影響するもの
Early Hints(回避策あり)と「Cloudflare ゾーン外のカスタムドメイン」。後者は本件では問題になりません(DNS を Cloudflare へ移管するため)。
2.4 【本命】リモート MCP サーバーを同居させる構成
WebMCP リモート MCP サーバー
実行場所 ユーザーのブラウザ内 Cloudflare のエッジ
呼び出し元 そのページを開いているブラウザ内エージェント Claude Desktop 等、インターネット上の任意の MCP クライアント
公開範囲 そのページを見た人だけ 誰でも(認証をかけない限り)
ホスティング依存 なし あり(ここが Workers の出番)
wrangler.jsonc で両立させる書き方
{
"name": "synon-corporate-site",
"compatibility_date": "2026-08-19",
"main": "./src/index.ts",
"assets": {
"directory": "./dist/",
"binding": "ASSETS",
"not_found_handling": "404-page",
"html_handling": "auto-trailing-slash",
"run_worker_first": ["/mcp", "/mcp/*"] // このパスだけ Worker に流す
}
}
run_worker_first にグロブ配列を指定すると、そのパスだけ Worker コードに流れ、それ以外はすべて静的アセットとして直接配信されます。 * は深いマッチ、! プレフィックスで否定を書けます。
MCP サーバー側の最小実装
// src/index.ts
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "synon-site", version: "1.0.0" });
server.registerTool(
"list_services",
{ description: "シンオン株式会社が提供するサービスの一覧を返す", inputSchema: {} },
async () => ({ content: [{ type: "text", text: "受託開発 / ..." }] })
);
return server;
}
const mcp = createMcpHandler(createServer);
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
if (url.pathname === "/mcp" || url.pathname.startsWith("/mcp/")) {
return mcp(request, env, ctx);
}
return env.ASSETS.fetch(request); // run_worker_first があれば通常ここには来ない
},
};
項目 状況
McpAgent クラス🔴 非推奨・機能凍結(deprecated and feature-frozen)。 新規は createMcpHandler を使う
トランスポート Streamable HTTP が現行推奨。 SSE は "deprecated in favor of Streamable HTTP" と公式に明記
Durable Objects の要否 createMcpHandler はデフォルトでステートレス なので不要 。状態が要る場合のみ DO / D1 / KV / R2
Durable Objects の Free 対応 ✅ Free プランで利用可能 (SQLite ストレージバックエンドのみ)。「Free だから DO が使えない」は誤り
MCP のディスカバリ規約 .well-known/mcp/server-cards.json が SEP-2127 として提案中・Draft 。現時点では設置不要
フェーズを分けてください
まず「静的サイトを Workers で配信する」ところまでを完了させ、MCP サーバーは別 PR・別フェーズ で足すのが安全です。最初から同時にやると、デプロイ失敗の切り分けが難しくなります。
2.5 WebMCP を実装する場合の実際
項目 状況(2026年8月)
仕様のステータス W3C Web Machine Learning Community Group の "Draft Community Group Report"。 「This is a discussion document and does not represent consensus of any W3C body 」と明記
提唱者 Google と Microsoft の共同イニシアチブ
ブラウザ実装 Chrome 149 で Origin Trial 進行中。 ローカル検証は chrome://flags/#enable-webmcp-testing
他ブラウザ Edge / Safari / Firefox の公式対応表明は確認できず
API 名に注意 — ブログ記事の情報は古いものが多い
// ❌ 古い情報。ブログ記事に多い
navigator.modelContext.registerTool(...)
// ✅ 現行仕様
document.modelContext.registerTool(...)
素の HTML/CSS/JS への組み込み例(プログレッシブエンハンスメント)
// src/assets/js/webmcp.js
(function () {
// 非対応ブラウザでは何もしない。通常のUIはそのまま機能する
if (!document.modelContext || typeof document.modelContext.registerTool !== 'function') return;
document.modelContext.registerTool({
name: 'get_company_profile',
description: 'シンオン株式会社の会社概要(所在地・設立・従業員数など)を返す',
inputSchema: { type: 'object', properties: {} },
execute: async () => ({
content: [{ type: 'text', text: document.querySelector('#company-profile').innerText }]
})
});
document.modelContext.registerTool({
name: 'search_works',
description: '開発実績をキーワードで検索する',
inputSchema: {
type: 'object',
properties: { query: { type: 'string', description: '検索キーワード' } },
required: ['query']
},
execute: async ({ query }) => {
const hits = [...document.querySelectorAll('[data-work]')]
.filter(el => el.innerText.includes(query))
.map(el => el.innerText.trim());
return { content: [{ type: 'text', text: hits.join('\n---\n') || '該当なし' }] };
}
});
})();
Chrome 公式ドキュメントによれば、標準の <form> 要素に属性で注釈を付ける「宣言的API」 も用意されており、こちらは静的サイトと相性が良い方式です(正確な属性構文は Chrome の該当ドキュメントで要確認)。
投資判断として正直に書いておきます
WebMCP は 2026年8月時点で Chrome の Origin Trial 段階 であり、他ブラウザの対応は未確認、仕様も Community Group の Draft です。実装しても効果が及ぶのは Origin Trial 参加者や将来のエージェント型ブラウザ利用者に限られます。 「先行事例を作る」という位置づけなら価値がありますが、サイト移行の必須要件に据えるのは時期尚早です。移行そのものを WebMCP のスケジュールに縛らないでください。
llms.txt について
Jeremy Howard 氏が2024年に提案したコミュニティ主導の非公式仕様で、W3C/IETF の標準ではありません。robots.txt(クローラーのアクセス制御)とも WebMCP とも別レイヤーです。静的ファイルとして置くだけなので、やるなら移行のついでに /llms.txt を1枚置く程度で十分 です。
3 前提条件 — 先に DNS を Cloudflare へ移管する
3.1 これは選択肢ではなく必須条件です
Cloudflare 公式ドキュメント
"You must have an active Cloudflare zone"
"you cannot create a Custom Domain ... on a zone you do not own"
現在 synon.co.jp の NS は ns1〜ns5.xserver.jp です。 この状態では Workers にカスタムドメインを割り当てられません。
3.2 synon.co.jp の DNS 移管が簡単な理由
論点 synon.co.jp syncsync.jp(比較)
MX が apex を指しているか ✅ いいえ (Google Workspace の5ホスト) 🔴 はい → apex プロキシで受信メール全滅
メールサーバの証明書問題 ✅ 無関係(Google が処理) 🔴 MTA-STS 不可
オリジンの SSL 自動更新 ✅ 移行後は問題そのものが消える (オリジンが Cloudflare) 🔴 Let's Encrypt 更新が最大リスク
DKIM の退避 ⚠️ 必要。 default._domainkey(Xserver 用)が存在する 🔴 必要
ワイルドカード ⚠️ あり ⚠️ あり
DKIM の退避だけは synon.co.jp でも必須です
既存資料の「訂正A」で判明したとおり、default._domainkey.synon.co.jp(Xserver が発行したもの)が存在します。NS 切替前に必ず控えてください。 手順は syncsync.jp 版 4.1.1節と同じです。
3.3 移管と本移行の順序
W1 DNS 棚卸し・DKIM 退避・TTL を 300 秒へ
W2 Cloudflare ゾーン作成 → 全レコードをグレー雲で1:1再現 → 旧NSと突合
W2 NS 切替(apex A は 203.0.113.10 = STUDIO のまま・グレー雲)
└ ここでは何も壊れない。STUDIO は今までどおり動く
W3 48時間の観察。メール送受信・サイト表示を確認
※ apex はグレー雲のまま。STUDIO 公式が「Cloudflare の DNS Proxy を無効に」
と明記しているため、STUDIO を使い続ける間はオレンジ雲にしない
────────── ここまでが既存資料の Step 1 ──────────
W4〜 新サイトを GitHub で構築(本書 4章)
W6 Workers へデプロイ、プレビューURLで検証(本書 5〜6章)
W7 apex A を削除し、Workers のカスタムドメインへ切替(本書 7章)
W8 STUDIO を解約(本書 7.4節)
DNS 移管の期間中、apex は「グレー雲のまま STUDIO を指す」状態にしてください
STUDIO 公式が「Cloudflare を使う場合は DNS Proxy モードを無効にしてください。有効だと証明書の発行・更新が正しく動作しない場合があります」と明記しているためです。オレンジ雲にするのは、オリジンが Workers に変わってからです。
どうしても DNS 移管を待てない場合の逃げ道
Workers の *.workers.dev プレビューURL、または別ドメイン で新サイトを先に作り込むことは可能です。カスタムドメインの割り当てだけが DNS 移管待ちになります。 開発・レビューは先行できます。
4 リポジトリとサイトの再構築(素の HTML / CSS / JS)
4.1 ディレクトリ構成
synon-corporate-site/
├── .github/workflows/
│ ├── deploy.yml # main への push → 本番デプロイ
│ └── preview.yml # PR → プレビューデプロイ
├── src/
│ ├── pages/ # 各ページの「中身だけ」を書く
│ │ ├── index.html
│ │ ├── about.html
│ │ ├── service.html
│ │ ├── development.html
│ │ ├── works-system.html
│ │ ├── work-app.html
│ │ ├── company-org.html
│ │ ├── topics.html
│ │ ├── contact.html
│ │ ├── privacy.html
│ │ ├── termsofuse.html
│ │ ├── topics/{dify,info}.html
│ │ └── posts/{zsFXXiMM,renew}.html
│ ├── partials/ # 共通パーツ
│ │ ├── head.html
│ │ ├── header.html
│ │ └── footer.html
│ ├── assets/
│ │ ├── css/{reset,style}.css
│ │ ├── js/{main,webmcp}.js
│ │ ├── img/ # STUDIO から回収した画像
│ │ └── fonts/
│ └── static/ # そのままコピーされるもの
│ ├── robots.txt
│ ├── favicon.ico
│ ├── _headers # Cloudflare のレスポンスヘッダ設定
│ └── _redirects # Cloudflare のリダイレクト設定
├── scripts/
│ ├── build.mjs # partials を差し込んで dist/ を作る(約60行)
│ └── sitemap.mjs # sitemap.xml を生成
├── dist/ # ビルド成果物(.gitignore する)
├── src/index.ts # 任意:MCP サーバー等(2.4節)
├── wrangler.jsonc
├── package.json
└── README.md
src/index.ts は最初は不要です
静的アセットだけの Worker としてデプロイできます。MCP サーバーやフォーム処理を足す段階で追加してください。
4.2 STUDIO からデザインとアセットを持ち出す — 何ができて何ができないか
4.2.1 前提となる事実
STUDIO 公式ヘルプの記載
「サイト自体のHTMLを直接書き換えることはできません。」
「サイトのHTMLコードを外部へエクスポートする機能は提供されていません。」
「Studioでは、CMSダッシュボードで管理するCMSのデータなどをエクスポートする機能は提供していません」
プランを問わず不可です。 つまり移行は「移植」ではなく「作り直し 」になります。
4.2.2 スクレイピングで持ち出すことの是非
条項 内容
第13条3項 「ユーザーは、本サービスを、当社が提供する状態でのみ利用するものとし、本サービスの複製、修正、変更、改変又は翻案を行ってはなりません。 」
第21条(7) 「本サービスを逆アセンブル、逆コンパイル、リバースエンジニアリング…」の禁止
「スクレイピング禁止」と名指しした条文は見当たりませんが
上記条項の適用範囲に触れる可能性があります。生成された HTML/CSS を一括ダウンロードして流用する手法は避けることを推奨します。 判断に迷う場合は STUDIO サポートに確認してください。
4.2.3 推奨する進め方 — 「著作物」と「生成物」を分けて考える
実務的な手順
全20ページのフルページスクリーンショットを撮る PC幅・タブレット幅・スマホ幅の3種類。これが「デザイン仕様書」の代わりになります。
DevTools で design token を実測する 色(Computed > color / background-color)、タイポグラフィ(font-family / font-size / line-height / font-weight)、余白のスケール、ブレークポイント。
トークンを CSS カスタムプロパティにまとめる 下記の例を参照。
共通パーツ(header / footer)を先に作り、トップページを完成させる
/* src/assets/css/style.css 冒頭 */
:root {
/* --- 実測した値をここに集約する --- */
--color-text: #1a1a1a;
--color-bg: #ffffff;
--color-accent: #0b5fff; /* ← DevTools の Computed から拾った実際の値に置き換える */
--color-muted: #6b7280;
--font-base: "Noto Sans JP", "Hiragino Kaku Gothic ProN", "Yu Gothic UI", Meiryo, sans-serif;
--fs-h1: clamp(28px, 5vw, 48px);
--fs-h2: clamp(22px, 3.5vw, 32px);
--fs-body: 16px;
--lh-body: 1.9; /* 日本語は 1.8〜2.0 が読みやすい */
--space-1: 8px; --space-2: 16px; --space-3: 24px;
--space-4: 40px; --space-5: 64px; --space-6: 96px;
--container: 1080px;
--bp-tablet: 768px;
--bp-desktop: 1024px;
}
この「トークンを先に決める」工程を飛ばさないでください
20ページを場当たりに書くと、後から色や余白を変えるときに20ファイルを触ることになります。
4.2.4 日本語サイトでの注意点
項目 注意
<html lang="ja">全ページで必須。共通パーツ化して漏れを防ぐ
<meta charset="UTF-8"><head> のできるだけ先頭 に置く
日本語 Web フォント Noto Sans JP をフルで読むと数MB になります。font-display: swap を必ず付け、可能なら unicode-range でサブセット化。そもそも OS 標準フォント(ヒラギノ / 游ゴシック / メイリオ)で済ませるのが最も速い という選択も現実的です
line-height日本語は欧文より広めが読みやすい(1.8〜2.0)
OGP 画像の文字 画像内に日本語を焼き込む場合、画像として作る (動的生成は不要。20ページなら手作りで足ります)
4.3 20ページの棚卸し — 作り直す前に必ずやる
1:1で作り直さないでください
実測で見つかった連番URLは、STUDIO 上でページを複製した痕跡の可能性が高いものです。
URL 確認すること 判断の選択肢
/1中身は何か。 テストページの可能性移植しない → 410 Gone または / へ 301
/service-1/service との違い統合 → 301 / 別ページとして残す
/development-1/development との違い同上
/work-1/works-system /work-app との違い同上
/company-1/about /company-org との違い同上
/topics と /posts/*なぜ2系統あるのか 1本化を推奨(8.1節で 301 設計)
20ページすべてをブラウザで開き、スプレッドシートに記録する 列:URL / ページタイトル / 用途 / 内部リンク元 / 外部からの被リンク有無 / 移行判断
Google Search Console で各URLの表示回数・クリック数を確認 流入がゼロのページは「統合 or 削除」の候補
サイト内の内部リンクを確認 どこからもリンクされていないページは孤立ページ
判断を3つに分類する 残す(同じURLを再現)/ 統合する(301)/ 廃止する(410 または / へ 301)
削除する場合も、必ず 301 か 410 を返してください
何もしないと 404 が量産され、Search Console にエラーが積み上がります。
4.4 共通パーツをどうするか — 「素の HTML」の唯一の弱点
20ページに同じヘッダー・フッターをコピペで20回書くと、メニューを1つ変えるのに20ファイルを直すことになります。 これが素の HTML の唯一かつ最大の弱点です。
案 内容 評価
A. 何もしない (20ファイルに複製)ビルド不要。dist/ = src/ ❌ 20ページでは破綻します。 5ページ以下なら可
B. 60行のビルドスクリプト Node.js の標準機能だけで <!--#include--> を展開 ✅ 推奨。 依存パッケージゼロ。中身が全部読める
C. JavaScript で fetch() して差し込む クライアント側で header/footer を読み込む ❌ SEO と表示速度が犠牲になります。 採用しないこと
案B の実装(scripts/build.mjs・依存パッケージなし)
// scripts/build.mjs
// src/pages/**/*.html を読み、<!--#include partial="xxx"--> を展開して dist/ へ出力する。
// src/assets, src/static はそのままコピーする。依存パッケージなし(Node.js 20+)。
import { readFile, writeFile, mkdir, readdir, cp, rm } from 'node:fs/promises';
import { join, dirname, relative } from 'node:path';
const SRC = 'src';
const OUT = 'dist';
/** ディレクトリを再帰的に走査して .html を列挙 */
async function walk(dir) {
const out = [];
for (const e of await readdir(dir, { withFileTypes: true })) {
const p = join(dir, e.name);
if (e.isDirectory()) out.push(...await walk(p));
else if (e.name.endsWith('.html')) out.push(p);
}
return out;
}
/** partials を読み込んでおく */
async function loadPartials() {
const dir = join(SRC, 'partials');
const map = {};
for (const e of await readdir(dir)) {
map[e.replace(/\.html$/, '')] = await readFile(join(dir, e), 'utf8');
}
return map;
}
/** <!--#include partial="header"--> を展開(入れ子も許可) */
function expand(html, partials, depth = 0) {
if (depth > 3) throw new Error('include の入れ子が深すぎます');
return html.replace(/<!--#include\s+partial="([\w-]+)"\s*-->/g, (_, name) => {
const p = partials[name];
if (p === undefined) throw new Error(`partial が見つかりません: ${name}`);
return expand(p, partials, depth + 1);
});
}
/** {{ title }} の変数展開(front matter 相当を HTML コメントで書く) */
function applyVars(html) {
const m = html.match(/^<!--\s*meta\s*([\s\S]*?)-->\s*/);
const vars = {};
if (m) {
for (const line of m[1].trim().split('\n')) {
const i = line.indexOf(':');
if (i > 0) vars[line.slice(0, i).trim()] = line.slice(i + 1).trim();
}
html = html.slice(m[0].length);
}
return html.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, k) => vars[k] ?? '');
}
await rm(OUT, { recursive: true, force: true });
await mkdir(OUT, { recursive: true });
const partials = await loadPartials();
const pages = await walk(join(SRC, 'pages'));
for (const file of pages) {
const raw = await readFile(file, 'utf8');
const html = applyVars(expand(raw, partials));
const rel = relative(join(SRC, 'pages'), file);
const dest = join(OUT, rel);
await mkdir(dirname(dest), { recursive: true });
await writeFile(dest, html, 'utf8');
console.log('build:', rel);
}
await cp(join(SRC, 'assets'), join(OUT, 'assets'), { recursive: true });
await cp(join(SRC, 'static'), OUT, { recursive: true });
console.log(`\n${pages.length} pages built into ${OUT}/`);
ページ側の書き方
<!-- src/pages/about.html -->
<!-- meta
title: 会社概要 | シンオン株式会社
description: シンオン株式会社の会社概要です。
path: /about
-->
<!DOCTYPE html>
<html lang="ja">
<head>
<!--#include partial="head"-->
</head>
<body>
<!--#include partial="header"-->
<main>
<h1>会社概要</h1>
<!-- ここだけ書けばよい -->
</main>
<!--#include partial="footer"-->
</body>
</html>
<!-- src/partials/head.html -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title }}</title>
<meta name="description" content="{{ description }}">
<link rel="canonical" href="https://synon.co.jp{{ path }}">
<meta property="og:title" content="{{ title }}">
<meta property="og:description" content="{{ description }}">
<meta property="og:url" content="https://synon.co.jp{{ path }}">
<meta property="og:type" content="website">
<meta property="og:image" content="https://synon.co.jp/assets/img/ogp.png">
<meta name="twitter:card" content="summary_large_image">
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" href="/assets/css/reset.css">
<link rel="stylesheet" href="/assets/css/style.css">
// package.json
{
"name": "synon-corporate-site",
"private": true,
"type": "module",
"scripts": {
"build": "node scripts/build.mjs && node scripts/sitemap.mjs",
"dev": "npm run build && wrangler dev",
"deploy": "npm run build && wrangler deploy"
},
"devDependencies": { "wrangler": "^4.0.0" }
}
これで「素の HTML/CSS/JS」という方針を保ったまま、共通パーツの一元管理ができます
ビルドスクリプトは60行で、全部読めば何が起きているか分かります。フレームワークのブラックボックスはありません。
4.5 CMS コンテンツ4件の扱い
「CMS 不要・開発者が git push」という方針は、4件なら成立します。 そのまま静的 HTML として書き起こしてください。
ただし合意を取っておくべきこと
今後お知らせを1件追加するたびに、開発者が HTML を書いて PR を出し、マージする 運用になります。
直近1年で追加されたのは 1件 (/posts/zsFXXiMM、2026-01-07)です。この頻度なら問題ありません
もし「月1回はお知らせを出したい」という話が出てきたら、その時点で見直してください。 選択肢は、①Markdown を content/topics/*.md に置いてビルド時に HTML 化する(build.mjs に30行追加で実装可能)②microCMS 等を足して Webhook で GitHub Actions を起動する、の2つです
今の判断を「将来も変えない」と決める必要はありません。 素の HTML で始めて、必要になったら①へ進むのが順当です
一覧ページ(/topics) も記事4件なら手書きで構いませんが、記事を追加したときに一覧の更新を忘れる のがよくある事故なので、ビルドスクリプトで自動生成しておくと安全です(src/pages/topics/ 配下の HTML を列挙して一覧を組み立てる、20行程度の追加)。
4.6 sitemap.xml / robots.txt / OGP / 構造化データ
sitemap.xml を生成する
現行 robots.txt が Sitemap: https://synon.co.jp/sitemap.xml を指しているので、同じパスで出す必要があります。 ただし STUDIO のようなインデックス形式にする必要はなく、単一ファイルで構いません。
// scripts/sitemap.mjs
import { readdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
const SITE = 'https://synon.co.jp';
const OUT = 'dist';
async function walk(dir, base = '') {
const out = [];
for (const e of await readdir(join(OUT, dir), { withFileTypes: true })) {
if (e.isDirectory()) {
if (['assets'].includes(e.name)) continue;
out.push(...await walk(join(dir, e.name), `${base}/${e.name}`));
} else if (e.name.endsWith('.html')) {
const slug = e.name === 'index.html' ? '' : '/' + e.name.replace(/\.html$/, '');
out.push(`${base}${slug}` || '/');
}
}
return out;
}
const urls = (await walk('')).sort();
const today = process.env.BUILD_DATE ?? new Date().toISOString().slice(0, 10);
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map(u => ` <url>
<loc>${SITE}${u}</loc>
<lastmod>${today}</lastmod>
</url>`).join('\n')}
</urlset>
`;
await writeFile(join(OUT, 'sitemap.xml'), xml, 'utf8');
console.log(`sitemap: ${urls.length} urls`);
robots.txt
# src/static/robots.txt
User-agent: *
Allow: /
Sitemap: https://synon.co.jp/sitemap.xml
構造化データ(JSON-LD)— 新サイトで追加する価値あり
実測では現行サイトに構造化データが確認できませんでした。 移行のついでに入れておくと、検索結果でのブランド表示に効きます。
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "シンオン株式会社",
"alternateName": "Synon Inc.",
"url": "https://synon.co.jp/",
"logo": "https://synon.co.jp/assets/img/logo.png",
"description": "ビジネスにおける時短サービスを提供",
"address": {
"@type": "PostalAddress",
"addressCountry": "JP",
"addressRegion": "大阪府"
},
"sameAs": []
}
</script>
address と sameAs は実際の値に置き換えてください
空のまま出すくらいなら項目ごと削除するほうが良いです。記事ページには BlogPosting、パンくずには BreadcrumbList を足せます。
OGP 画像
現行の og:image URL から画像をダウンロード
src/assets/img/ogp.png として配置(推奨サイズ 1200×630)
partials/head.html の og:image を https://synon.co.jp/assets/img/ogp.png に変更
移行後に検証: X (Twitter) Card Validator / Facebook Sharing Debugger(キャッシュの再取得も実施)/ Slack に URL を貼ってプレビュー確認
4.7 /contact フォームの扱い(参考 — 本書のスコープ外だが必須事項)
本書の章立てには含めていませんが、/contact が実在する以上、避けて通れません
STUDIO Form は移行と同時に失われます。 まず回答データを CSV でエクスポート してください(回答一覧画面右上の3点メニューから可能)。
案 内容 費用 評価
A Workers + Resend 等の外部メールAPI Workers Free + Resend 無料枠 ✅ 推奨。 Cloudflare 公式チュートリアルあり
B Workers + Cloudflare Email Sending 🔴 Workers 有料プラン限定 ($5/月〜) ⚠️ 有料化が必要
C 外部フォームSaaS(Formspree 等) サービス次第 ○ 実装は最小
D Google Forms 埋め込み ¥0 △ デザインが合わない
MailChannels は使えません
「Cloudflare Workers から無料でメールを送る」手法として日本語記事に多数出てきますが、2024年8月31日に提供終了 しています。古い記事を参考にしないでください。
Turnstile を必ず併用してください。 既存資料(synon.co.jp 版 Step 5)で「社内標準部品」として整備済みのものが、そのまま使えます。
// src/index.ts に追記(run_worker_first に "/api/*" を追加すること)
if (url.pathname === '/api/contact' && request.method === 'POST') {
const form = await request.formData();
// 1. Turnstile 検証(既存資料 Step 5.4 と同じ)
const verify = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: new URLSearchParams({
secret: env.TURNSTILE_SECRET,
response: String(form.get('cf-turnstile-response') ?? ''),
remoteip: request.headers.get('CF-Connecting-IP') ?? '',
}),
}).then(r => r.json<any>());
if (!verify.success || verify.hostname !== 'synon.co.jp' || verify.action !== 'contact') {
return new Response('Bad Request', { status: 400 });
}
// 2. メール送信(Resend の例)
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'noreply@synon.co.jp',
to: 'info@synon.co.jp',
subject: `【お問い合わせ】${form.get('name')} 様`,
text: [...form.entries()].map(([k, v]) => `${k}: ${v}`).join('\n'),
}),
});
return Response.redirect('https://synon.co.jp/contact/thanks', 303);
}
SPF のルックアップ数に注意
noreply@synon.co.jp から送る場合、SPF / DKIM を Resend 側の指示どおり設定する必要があります。既存資料のとおり synon.co.jp の SPF はすでに 8/10 ルックアップ で余裕がありません。Resend の include: を足す前に、まず既存資料の SPF 整理を先に実施してください。
5 Cloudflare Workers の設定
5.1 wrangler.jsonc(完全版)
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "synon-corporate-site",
"compatibility_date": "2026-08-19",
// 静的アセットのみの構成では "main" は不要。
// MCP サーバーやフォーム API を足す段階でコメントを外す。
// "main": "./src/index.ts",
"assets": {
"directory": "./dist/",
// Worker コードから env.ASSETS.fetch() でアセットを読むための binding
"binding": "ASSETS",
// 一致するアセットが無いとき、最も近い 404.html を 404 ステータスで返す。
// 取りうる値: "none"(既定) / "404-page" / "single-page-application"
"not_found_handling": "404-page",
// 個別ファイル(foo.html)はスラッシュなし、フォルダ配下はスラッシュ付きで配信。
// 取りうる値: "auto-trailing-slash"(既定) / "force-trailing-slash"
// / "drop-trailing-slash" / "none"
"html_handling": "auto-trailing-slash"
// MCP サーバーやフォーム API を足す段階で有効化する。
// "run_worker_first": ["/mcp", "/mcp/*", "/api/*"]
},
"observability": { "enabled": true }
}
account_id は書かないでください
CI からは CLOUDFLARE_ACCOUNT_ID をシークレット経由で渡します(6.3節)。設定ファイルに書くとリポジトリに残ります。
5.2 assets 各オプションの意味
not_found_handling
値 挙動 本件での採用
none(既定)一致しなければ Worker へ。Worker も無ければ空の 404 ❌
404-page最も近い 404.html の内容を 404 Not Found で返す ✅ 採用
single-page-application一致しなければ /index.html を 200 OK で返す ❌ SPA ではない
dist/404.html を必ず用意してください。 用意しないと存在しない URL で素っ気ない画面が出ます。
html_handling
値 挙動
auto-trailing-slash (既定・推奨)foo.html → /foo、foo/index.html → /foo/
force-trailing-slashすべて末尾スラッシュ付きへ 307 リダイレクト
drop-trailing-slashすべて末尾スラッシュなしへ 307 リダイレクト
none組み込み処理を無効化し、完全に自前で制御
既存 URL の形と揃えてください
現行 STUDIO の URL は
https://synon.co.jp/about(
スラッシュなし )です。
src/pages/about.html を置けば
auto-trailing-slash で
/about として配信され、
現行と一致します。
一方 /topics/dify は src/pages/topics/dify.html に置けば /topics/dify になります(topics/dify/index.html にすると /topics/dify/ になってしまうので注意)。
run_worker_first
"run_worker_first": ["/mcp", "/mcp/*", "/api/*", "!/api/docs/*"]
true を指定すると全リクエストが Worker を経由 します(課金対象の実行回数が増えるので避ける)
グロブ配列で指定するのが正解。 * は深いマッチ、! プレフィックスで除外
指定しなければ静的アセットが Worker より先に配信される (=この構成のデフォルト)
Pages からの移行時の落とし穴
Pages は Functions を静的アセットより先に実行していましたが、Workers は静的アセットが先 です。挙動を Pages に合わせたいときだけ run_worker_first を使います。
5.3 _headers と _redirects
どちらも静的アセットディレクトリ直下 (= dist/ 直下)に置きます。本構成では src/static/ に置いておけばビルド時にコピーされます。
src/static/_headers
# ============================================
# 全ページ共通のセキュリティヘッダ
# ============================================
/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
X-Frame-Options: SAMEORIGIN
# ============================================
# 静的アセットは長期キャッシュ
# ============================================
/assets/*
Cache-Control: public, max-age=31536000, immutable
# ============================================
# HTML は短めに(更新をすぐ反映させたい)
# ============================================
/*.html
Cache-Control: public, max-age=0, must-revalidate
項目 上限
ルール数 100
1行の文字数 2,000文字
Worker が生成したレスポンス 適用されない (_headers は静的アセットのみ)
ヘッダーの削除 ! プレフィックスで可能
2つの注意
Cache-Control: public, no-transform を付けないでください。 これが付いていると Cloudflare Web Analytics のビーコン自動注入が失敗します (既存資料 Step 6.1 参照)
Content-Security-Policy は最初は入れないでください。 入れるなら Content-Security-Policy-Report-Only で1〜2週間観測してから昇格させます。いきなり入れると WebMCP のスクリプトや外部フォントが黙ってブロックされます
src/static/_redirects
# 形式: [source] [destination] [code]
# code を省略すると 302。恒久移転は必ず 301 を明示すること。
# --- STUDIO 時代の重複ページを統合 ---
/1 / 301
/service-1 /service 301
/development-1 /development 301
/work-1 /works-system 301
/company-1 /about 301
# --- 記事系を1本化する場合 ---
/posts/renew /topics/renew 301
/posts/zsFXXiMM /topics/zsFXXiMM 301
# --- STUDIO 固有のサイトマップURL(Search Console 登録済みの可能性)---
/sitemap-static.xml /sitemap.xml 301
/sitemap-dynamic/* /sitemap.xml 301
項目 上限
静的リダイレクト 2,000件
動的リダイレクト(:param や * を含むもの) 100件
合計 2,100件
1行の文字数 1,000文字
本件は20URL規模なので、上限は問題になりません
5.4 カスタムドメインの設定
前提:synon.co.jp が Cloudflare 上のアクティブなゾーンであること (3章)。
Cloudflare ダッシュボード > Compute (Workers) > synon-corporate-site
Settings > Domains & Routes > Add > Custom Domain
synon.co.jp を入力 > Add Domain
続けて www.synon.co.jp も追加(または Redirect Rules で apex へ 301 — 8.3節)
Cloudflare が自動で行うこと
"Cloudflare will create DNS records and issue necessary certificates on your behalf"
DNS レコード(CNAME)が自動作成されます。 既存の A 203.0.113.10 は手動で削除 してください
Advanced Certificate が対象ホスト名向けに自動発行されます
ここが STUDIO との決定的な違いです
オリジンが Cloudflare 自身になるため、Origin CA も、オリジン証明書の有効期限監視も、526 エラーの心配も、すべて不要になります。 syncsync.jp 版で最大のリスクだった論点が、synon.co.jp では丸ごと消えます。
メールへの影響
Cloudflare の DNS 仕様:
"Only records used for IP address resolution — A, AAAA, and CNAME records — can be proxied. Other record types (such as MX or TXT) are always DNS-only."
synon.co.jp の MX は Google Workspace の5ホストを指しており、apex A とは独立しています。カスタムドメイン設定でメールが壊れることはありません。
5.5 ローカル開発
npm ci # 依存インストール
npm run dev # ビルド + ローカルサーバ(http://localhost:8787)
npm run build # ビルドのみ
npx wrangler deploy # 手動デプロイ(通常は GitHub Actions に任せる)
# デプロイ前のローカル確認
npx linkinator dist --recurse --skip "^https://" # リンク切れ
npx html-validate "dist/**/*.html" # HTML 構文
find dist -type f | sort # 生成物の目視
ポート番号に注意
wrangler dev は 8787番 です(wrangler pages dev は 8788番でした。Pages の記事を見て混乱しないよう注意)。
5.6 Free プランの制限(本件で意識すべきもの)
項目 Free の上限 本件の状況
静的アセットへのリクエスト 課金対象外・無制限 ✅ 余裕
ファイル数 / バージョン 20,000ファイル ✅ 20ページ + 画像。全く問題なし
1ファイルの最大サイズ 25 MiB ✅ 画像を圧縮していれば問題なし
Worker の実行回数 100,000リクエスト/日 ✅ MCP を足しても十分
ロールバック可能なバージョン数 直近100 ✅
Durable Objects 利用可 (SQLite バックエンドのみ)✅ MCP で必要になっても Free で足りる
要検証
静的アセットの「合計サイズ上限 」は公式ドキュメントに明記が見つかりませんでした。20ページのコーポレートサイトで問題になる規模ではありませんが、動画などの大容量ファイルを大量に置く場合は事前に確認してください。
6 GitHub Actions で自動デプロイ
6.1 全体像
6.2 Cloudflare API トークンの作成(最小権限)
「Edit Cloudflare Workers」テンプレートは使わないでください
このテンプレートには Workers Scripts Edit / Workers Routes Edit / Workers KV Storage Edit / Workers Tail Read / Workers R2 Storage Edit / Account Settings Read / User Details Read / Memberships Read がすべて含まれ、本件には過剰です。
Cloudflare ダッシュボード
→ 右上のアカウントアイコン > My Profile > API Tokens
→ Create Token > Custom token > Get started
【Permissions】
Account │ Workers Scripts │ Edit ← 必須
Account │ Account Settings │ Read ← 推奨
Zone │ Workers Routes │ Edit ← CI からルートを操作する場合のみ
【Account Resources】
Include │ <対象アカウント>
【Zone Resources】
Include │ Specific zone │ synon.co.jp ← 全ゾーンにしない
【Client IP Address Filtering】
空欄のまま ← GitHub-hosted runner は IP が変動するため
【TTL】
開始日〜終了日を設定(推奨: 1年) ← 期限切れ日をカレンダーに登録すること
2つの補足
Zone > Workers Routes > Edit は、カスタムドメインをダッシュボードで手動設定して以後変更しないなら不要です。まず付けずに作り、デプロイでエラーが出たら追加する 運用が最小権限に近づきます
🔴 TTL を設定した場合、期限切れ日に必ず自動デプロイが止まります。 期限の1ヶ月前をカレンダーに登録し、ローテーション手順を運用計画へ追記してください(9.5節)
Account ID の取得: クイック検索(Cmd/Ctrl + K)で「Copy account ID」/ Compute (Workers) セクションの「Account Details」/ 任意のドメインの Overview ページ下部「API」セクション。
6.3 GitHub Secrets と Environments
Repository secrets Environment secrets(推奨)
アクセス範囲 全ワークフロー・全ブランチ・PR含む environment: を指定したジョブのみ
承認との連動 なし required reviewers の承認後にのみ解放される
本件での用途 ❌ ✅
GitHub リポジトリ > Settings > Environments > New environment
【production】
Secrets:
CLOUDFLARE_API_TOKEN = <6.2 で作ったトークン>
CLOUDFLARE_ACCOUNT_ID = <Account ID>
Protection rules:
☑ Required reviewers … 1〜2名(本番デプロイの承認者)
☑ Deployment branches … Selected branches → main のみ
□ Wait timer … 不要
【preview】
Secrets:
CLOUDFLARE_API_TOKEN = <同じ、または別トークン>
CLOUDFLARE_ACCOUNT_ID = <Account ID>
Protection rules:
(なし。PR ごとに自動デプロイさせる)
required reviewers の効果
main にマージしても「承認するまで本番に出ない」 状態を作れます。コーポレートサイトのように「間違った内容を出したくない」ものには有効です。まず設定しておいて、運用が回るようになったら外す のが安全側です。
6.4 本番デプロイ用ワークフロー(完全版)
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
workflow_dispatch: # 手動実行も可能にしておく
# 同時実行を防ぐ。本番は進行中のデプロイをキャンセルしない
concurrency:
group: deploy-production
cancel-in-progress: false
permissions:
contents: read
jobs:
# ---------- 1. ビルドと検証 ----------
build:
name: Build & Verify
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Validate HTML
run: npx --yes html-validate "dist/**/*.html"
continue-on-error: true # 最初は警告のみ。安定したら外す
- name: Check for broken internal links
run: npx --yes linkinator dist --recurse --skip "^https?://"
continue-on-error: true
- name: Verify required files exist
run: |
set -e
for f in dist/index.html dist/404.html dist/robots.txt dist/sitemap.xml; do
[ -f "$f" ] || { echo "::error::missing $f"; exit 1; }
done
echo "file count: $(find dist -type f | wc -l)"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7
# ---------- 2. デプロイ ----------
deploy:
name: Deploy
needs: build
runs-on: ubuntu-latest
environment:
name: production
url: https://synon.co.jp/
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Deploy to Cloudflare Workers
id: deploy
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy
- name: Smoke test
run: |
set -e
sleep 10
for path in / /about /service /topics /contact /sitemap.xml /robots.txt; do
code=$(curl -sS -o /dev/null -w '%{http_code}' "https://synon.co.jp${path}")
echo "${path} → ${code}"
[ "$code" = "200" ] || { echo "::error::${path} returned ${code}"; exit 1; }
done
# 404 が正しく 404 を返すこと
code=$(curl -sS -o /dev/null -w '%{http_code}' "https://synon.co.jp/zzq7x9-nonexistent")
[ "$code" = "404" ] || { echo "::error::404 handling broken (got ${code})"; exit 1; }
- name: Rollback on smoke test failure
if: failure()
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: rollback --message "CI: smoke test failed, auto rollback"
要検証
wrangler rollback の対話プロンプトを抑止するフラグ(--yes など)は公式ドキュメントで確認しきれませんでした。実装時に npx wrangler rollback --help で確認し、必要なら追加してください。CI で対話待ちになるとジョブがタイムアウトします。
6.5 プレビュー用ワークフロー(完全版)
# .github/workflows/preview.yml
name: Deploy Preview
on:
pull_request:
types: [opened, synchronize, reopened]
concurrency:
group: preview-${{ github.event.pull_request.number }}
cancel-in-progress: true # PR は最新のみ有効
permissions:
contents: read
pull-requests: write
jobs:
preview:
name: Preview
# フォークからの PR ではシークレットが渡らないためスキップする
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
environment:
name: preview
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- run: npm ci
- run: npm run build
- name: Upload preview version
id: preview
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: versions upload --preview-alias pr-${{ github.event.pull_request.number }}
- name: Comment preview URL
uses: actions/github-script@v7
with:
script: |
const alias = `pr-${context.payload.pull_request.number}`;
const body = [
'### 🔍 プレビューをデプロイしました',
'',
`**Preview alias:** \`${alias}\``,
'',
'```',
`${{ steps.preview.outputs.command-output }}`.trim().slice(0, 1500),
'```',
'',
'<sub>上のログの Preview URL を開いて確認してください。push のたびに更新されます。</sub>',
].join('\n');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('プレビューをデプロイしました'));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner, repo: context.repo.repo,
comment_id: existing.id, body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number, body,
});
}
種類 生成方法 用途
Versioned Preview URL wrangler deploy / versions upload で自動生成 バージョンごとの固定URL
Aliased Preview URL wrangler versions upload --preview-alias <名前>PR ごとの人間が読めるURL(本件で採用)
2つの補足
エイリアスの命名規則: 小文字英数字とダッシュのみ、小文字始まり 。pr-123 は適合します。保持されるのは直近1,000個 まで
versions upload は本番トラフィックに影響しません。 アップロードするだけで、本番へ反映するには wrangler deploy または versions deploy が必要です
6.6 セキュリティ上の必須事項
# 事項 対応
1 🔴 pull_request_target を使わない フォークPRのコードをチェックアウトして実行する構成と組み合わせると、CLOUDFLARE_API_TOKEN を窃取されます (Pwn Request)。本書のワークフローは pull_request のみを使用
2 🔴 フォークPRではスキップする 6.5節の if: github.event.pull_request.head.repo.full_name == github.repository がこれ。シークレットが渡らないので、書かないとジョブが不可解に失敗します
3 permissions: を最小にワークフロー冒頭で contents: read。書き込みが要るジョブだけ個別に追加
4 リポジトリ既定を read-only に Settings > Actions > General > Workflow permissions → Read repository contents permission
5 サードパーティ Action を SHA でピン留め タグは移動可能なので、悪意あるコードに差し替えられ得ます
6 🔴 OIDC は現時点で使えない Cloudflare は GitHub Actions の OIDC(短命クレデンシャル)に未対応 (機能リクエストが提出されている段階)。長期トークン + TTL + Environment 承認で緩和する
# ❌ タグは移動可能
- uses: cloudflare/wrangler-action@v4
# ✅ フルレングスのコミットSHAで固定し、コメントでバージョンを残す
- uses: cloudflare/wrangler-action@0123456789abcdef0123456789abcdef01234567 # v4.0.0
# SHA の調べ方
git ls-remote https://github.com/cloudflare/wrangler-action refs/tags/v4.0.0
Dependabot を有効にしておくと
SHA ピン留めのまま更新 PR を自動作成してくれます。.github/dependabot.yml に package-ecosystem: "github-actions" を追加してください。
6.7 なぜ Workers Builds(Cloudflare の Git 連携)ではなく GitHub Actions か
項目 Workers Builds GitHub Actions(本書の採用)
セットアップ ✅ 最小限(リポジトリを繋ぐだけ) ⚠️ workflow を書く必要がある
ビルド分数 Free 3,000分/月 、同時ビルド 1 Free 2,000分/月(プライベート)、パブリックは無制限
ビルドタイムアウト 20分 6時間
デプロイ前の検証 ⚠️ ビルドコマンド内に押し込む形になる ✅ ジョブとして分離でき、失敗時に止められる
承認フロー ❌ ✅ GitHub Environments の required reviewers
失敗時の自動ロールバック ❌ ✅ 実装できる
他の CI と統合 ❌ ✅
本件で GitHub Actions を選ぶ理由
「push したら出る」だけなら Workers Builds で十分ですが、コーポレートサイトは「間違った内容が出ること」のコストが高い ため、①デプロイ前の検証 ②本番デプロイの承認 ③スモークテスト失敗時の自動ロールバック、の3つを入れられる GitHub Actions を採用します。
両方を同時に有効にしないでください
二重デプロイになり、どちらが反映されたか分からなくなります。GitHub Actions を使うなら、Cloudflare 側の Git 連携は接続しないこと。
7 DNS 切替とロールバック
7.1 切替前チェックリスト
すべてにチェックが付くまで切り替えないでください。
新サイト側
20URL の棚卸しが完了し、「残す / 統合 / 廃止」が確定している(4.3節)
プレビューURL で全ページを目視確認した(PC / タブレット / スマホ)
dist/404.html が存在し、存在しないURLで 404 が返る
dist/robots.txt が存在し、sitemap.xml を指している
dist/sitemap.xml が生成され、URL がすべて新サイトのもの
_redirects に旧URLからの 301 が全件入っている(8.1節)
OGP 画像が自社ドメイン配下にあり、SNS のプレビューで表示される
画像アセットをすべて STUDIO から回収済み(storage.googleapis.com への参照がゼロ)
/contact フォームの代替が動作する(4.7節)
Google Analytics 等の計測タグを移植した
Cloudflare 側 / DNS 側 / STUDIO 側
synon.co.jp が Cloudflare 上のアクティブなゾーンである
Worker がデプロイ済みで、*.workers.dev で正常に表示される
主要レコードの TTL を 300 秒に下げてある(48時間前までに)
現在の全レコードをエクスポート済み(切り戻し用)
MX(Google Workspace の5レコード)に手を触れていない
STUDIO は有料プランのまま維持している(解約していない)
フォーム回答を CSV でエクスポート済み
CMS 4件の本文を控えてある
7.2 切替手順
この移行の良いところ
「apex A レコードを1本差し替えるだけ」で完了します。 MX が独立しているため、メールへの影響を考える必要がありません。
Step 2 で www を先に切り替えるのが最大のコツです
本番 URL(apex)に影響を与えずに、実ドメイン・実証明書で新サイトを検証できます。*.workers.dev では確認できない「カスタムドメインでの挙動」(証明書、リダイレクト、_headers)をここで潰せます。
7.3 ロールバック
# 症状 切り戻し 所要
1 新サイトの内容に問題がある (表示崩れ、誤字、リンク切れ)wrangler rollback または ダッシュボード > Deployments > 対象バージョン > Rollback即時 (直近100バージョンから選択可能)
2 新サイト全体がダメで、STUDIO に戻したい Cloudflare > DNS で カスタムドメインを削除 → A synon.co.jp → 203.0.113.10 を再作成(グレー雲) 数分 (TTL 300秒)
3 DNS 移管そのものに問題がある NS を Xserver に戻す 最大24時間
# #1 のコマンド
npx wrangler rollback # 対話モードで直近のバージョンから選ぶ
npx wrangler deployments list # バージョン一覧
npx wrangler rollback <VERSION_ID> # ID を直接指定
# #2 の手順(手順書として印刷しておくこと)
1. Cloudflare > Compute (Workers) > synon-corporate-site
> Settings > Domains & Routes
→ synon.co.jp の Custom Domain を Remove
2. Cloudflare > synon.co.jp > DNS > Records > Add record
Type : A
Name : @
IPv4 : 203.0.113.10
Proxy : DNS only(グレー雲)★必ずグレー
TTL : Auto
3. dig +short A synon.co.jp が 203.0.113.10 を返すまで待つ(数分)
4. https://synon.co.jp/ で STUDIO のサイトが表示されることを確認
#2 が成立する条件は「STUDIO を解約していないこと」です
だから 7.4節が重要になります。また、必ずグレー雲にしてください。 STUDIO 公式が「Cloudflare を使う場合は DNS Proxy モードを無効に」と明記しています。オレンジ雲で戻すと証明書関連で別の問題が起きます。
7.4 STUDIO の解約タイミング 🔴
STUDIO 公式ヘルプの記載
Free プランへダウングレードすると「
公開中のサイトは自動的に非公開になる 」
月額プランへ切り替える際は「ドメインの再接続とサイトの再公開が必要で、再公開が完了するまでサイトは閲覧できない」
つまり、解約した瞬間に切り戻し先が消えます。
切替当日 apex を Workers へ。STUDIO は有料プランのまま維持
↓
+2週間 Search Console のクロールエラーがゼロ、
Analytics のトラフィックが移行前と同水準であることを確認
↓
+1ヶ月 主要な検索キーワードでの順位が落ちていないことを確認
↓
解約 ここで初めて STUDIO を解約する
1ヶ月分の STUDIO 料金は「保険料」だと考えてください
¥1,720〜5,460 で「1ヶ月いつでも切り戻せる」状態が買えます。安い保険です。
解約前に必ず実施
フォーム回答の CSV エクスポート(一度解約すると取り出せません)
CMS 4件の本文・画像の控え
アクセス解析データはエクスポートできません (公式FAQ:「現在、アナリティクスのデータは出力できません」)。必要ならスクリーンショットで残す
STUDIO 側の独自ドメイン設定を解除(公開パネルのドメイン欄の [×])
8 SEO ・ 既存URL維持 ・ リダイレクト設計
8.1 URL マッピング表(実測20URL・全件)
この表を埋めることが、SEO を落とさないための唯一の作業です。
# 旧URL(STUDIO) 新URL 処理 備考
1 //維持 —
2 /about/about維持 —
3 /company-org/company-org維持 —
4 /company-1🔍 要判断 301 → /about 内容を確認して決定
5 /service/service維持 —
6 /service-1🔍 要判断 301 → /service 同上
7 /development/development維持 —
8 /development-1🔍 要判断 301 → /development 同上
9 /works-system/works-system維持 —
10 /work-app/work-app維持 —
11 /work-1🔍 要判断 301 → /works-system 同上
12 /topics/topics維持 一覧ページ
13 /contact/contact維持 フォーム実装は 4.7節
14 /privacy/privacy維持 —
15 /termsofuse/termsofuse維持 —
16 /1— 301 → / または 410 テストページの可能性
17 /topics/dify/topics/dify維持 —
18 /topics/info/topics/info維持 —
19 /posts/renew/topics/renew301 記事系を1本化する場合
20 /posts/zsFXXiMM/topics/<意味のあるslug>301 自動生成スラッグを改名する場合
+ /sitemap-static.xml/sitemap.xml301 Search Console 登録済みの可能性
+ /sitemap-dynamic/*/sitemap.xml301 同上
19・20 の判断は慎重に
URL を変えると被リンクとインデックスが一度リセットされます。301 を張れば評価は引き継がれますが、リダイレクトを永久に維持する必要があります。 「きれいな URL にしたい」という理由だけで変えるのは、20件程度の記事では割に合わないことが多いです。迷ったら維持してください。
/1 のような明らかなテストページは 410 Gone が正解です
301 で / に飛ばすと、Google は「関連性のないリダイレクト」としてソフト404 扱いすることがあります。410 は「意図的に削除した」という明確なシグナルです。
8.2 リダイレクトの実装手段 — 3つの選択肢
手段 場所 Free の上限 本件での採用
_redirects ファイルdist/_redirects(Git 管理)静的 2,000 + 動的 100 、1行 1,000文字 ✅ 採用
Redirect Rules ダッシュボード(Rules) 件数は別途上限あり(要検証 ) ○ www → apex の1本だけ ここで実装するのも可
Bulk Redirects ダッシュボード(リスト管理) ルール 15 / リスト 5 / URL 10,000件 ❌ 本件の規模では不要
_redirects を第一選択にしてください
理由は「リダイレクト設定がコードと同じリポジトリに入り、PR でレビューでき、ロールバックできる 」から。ダッシュボードで設定したものは Git に残らず、誰がいつ変えたか追えません。
_redirects では 410 を返せません
/1 を 410 にしたい場合は
src/index.ts で処理し、
run_worker_first に
/1 を追加してください。
if (url.pathname === '/1') {
return new Response('Gone', { status: 410 });
}
面倒なら 404 のままでも実害は小さい です(_redirects に何も書かなければ 404 になります)。
8.3 www とワイルドカードの扱い
【現状の問題】
synon.co.jp A 203.0.113.10 ← STUDIO
www.synon.co.jp A 198.51.100.10 ← Xserver(apex と別基盤!)
*.synon.co.jp A 203.0.113.10 ← ワイルドカード
ホスト 設定 理由
synon.co.jp(apex)Workers カスタムドメイン (正規)—
www.synon.co.jpWorkers カスタムドメイン + Redirect Rule で apex へ 301 既存の被リンク・ブックマークを拾う
*.synon.co.jp🔍 判断が必要 (下記) —
# www → apex の 301(Redirect Rules)
Cloudflare > synon.co.jp > Rules > Redirect Rules > Create rule
Rule name : www to apex
When incoming requests match:
Field : Hostname
Operator : equals
Value : www.synon.co.jp
Then:
Type : Dynamic
Expression : concat("https://synon.co.jp", http.request.uri.path)
Status code : 301
Preserve query string : ON
案 内容 評価
A 削除する ✅ 推奨。 フィッシング(login-verify.synon.co.jp 等)の余地を断てる。実在サブドメインは www のみと判明しているので安全
B Workers へ向ける ⚠️ Free の Universal SSL は1階層まで しかカバーしない。a.b.synon.co.jp は TLS エラー
C 現状維持 🔴 解約後にダングリング確定。絶対に避ける
C は選ばないでください
STUDIO を解約したあともワイルドカードが 203.0.113.10(STUDIO の共有IP )を指し続けると、他人のサイトが自社ドメインで表示される 可能性があります。移行時に必ず削除または付け替えてください。
ワイルドカードを削除できたら
DMARC に np=reject を付けられます(存在しないサブドメインを騙るなりすましを拒否)。既存資料の DMARC ランプアップと合わせて実施してください。
8.4 sitemap.xml と robots.txt
項目 現行 新サイト
robots.txt の場所/robots.txt同じ (src/static/robots.txt)
Sitemap: の指す先https://synon.co.jp/sitemap.xml同じ
sitemap の構造 インデックス形式 (子3ファイル)単一ファイルで可
子サイトマップのURL /sitemap-static.xml 他301 → /sitemap.xml
インデックス形式にする必要はありません
Google のサイトマップ上限は 50,000 URL / 50MB です。20URL なら単一ファイルで十分です。
8.5 canonical・OGP・構造化データ
項目 対応
canonical 全ページに <link rel="canonical" href="https://synon.co.jp{{ path }}">。www 付きにしないこと 。4.4節の partials/head.html で一元管理
OGP og:url も canonical と同じ apex 表記に統一。og:image は自社ドメイン配下(4.6節)
構造化データ 現行になし。移行のついでに Organization を追加 (4.6節)
hreflang日本語のみなので不要
<meta name="robots">現行は all。新サイトでは省略してよい(既定が index,follow)
8.6 Search Console でやること
移行前
「検索パフォーマンス」を CSV エクスポート 移行後の比較用ベースライン
「ページのインデックス登録」でインデックス済みURL一覧を確認 sitemap に載っていないURLが無いか(実測20件以外がないか)確認
移行直後
新しい sitemap.xml を送信 Search Console > サイトマップ
旧サイトマップURL(sitemap-static.xml 等)を削除
URL 検査ツールで主要ページの「インデックス登録をリクエスト」 / /about /service /works-system の4本程度
移行後 1〜4週間
「ページのインデックス登録」で 404 / リダイレクトエラーが増えていないか毎週確認
検索パフォーマンスをベースラインと比較 表示回数が2週間以上 20%以上落ちたら要調査
ドメインは変わらないので「アドレス変更ツール」は不要です
同一ドメイン内のホスティング変更なので、Google 側の手続きは sitemap の再送信だけで足ります。
一時的に順位が揺れるのは正常です。 HTML 構造が変わるため、Google の再評価に2〜4週間かかります。1週間で判断しないでください。
8.7 移行後の確認スクリプト
#!/bin/bash
# post-migration-check.sh ── 移行直後と、その後1週間毎日実行する
SITE=https://synon.co.jp
echo "=== 1. 主要ページが 200 を返すか ==="
for p in / /about /company-org /service /development /works-system /work-app \
/topics /topics/dify /topics/info /contact /privacy /termsofuse; do
printf "%-24s %s\n" "$p" "$(curl -sS -o /dev/null -w '%{http_code}' "$SITE$p")"
done
echo "=== 2. リダイレクトが 301 で正しい先へ飛ぶか ==="
for p in /1 /service-1 /development-1 /work-1 /company-1 \
/posts/renew /posts/zsFXXiMM /sitemap-static.xml; do
printf "%-24s %s → %s\n" "$p" \
"$(curl -sS -o /dev/null -w '%{http_code}' "$SITE$p")" \
"$(curl -sS -o /dev/null -w '%{redirect_url}' "$SITE$p")"
done
echo "=== 3. www が apex へ 301 するか ==="
curl -sS -o /dev/null -w '%{http_code} → %{redirect_url}\n' https://www.synon.co.jp/
echo "=== 4. 存在しないURLが 404 を返すか ==="
curl -sS -o /dev/null -w '%{http_code}\n' "$SITE/zzq7x9-nonexistent"
echo "=== 5. sitemap / robots ==="
curl -sS -o /dev/null -w 'sitemap.xml %{http_code}\n' "$SITE/sitemap.xml"
curl -sS -o /dev/null -w 'robots.txt %{http_code}\n' "$SITE/robots.txt"
echo "=== 6. STUDIO への参照が残っていないか ==="
curl -sS "$SITE/" | grep -io "storage.googleapis.com\|studio.design" || echo " 参照なし: OK"
echo "=== 7. ワイルドカードが消えているか ==="
dig +short A zzq7x9-none.synon.co.jp || echo " NXDOMAIN: OK"
echo "=== 8. メールが壊れていないか ==="
dig +short MX synon.co.jp
9 既存 Cloudflare 資料との整合・訂正
9.1 🔵 訂正B(STUDIO と Origin CA)が根本解決します
既存資料(syncsync.jp 版 9.4節)で挙げていた訂正事項
訂正B: 「オリジンは AWS EC2」→ 実際には synon.co.jp の apex は STUDIO(Google Cloud, 203.0.113.10) 。STUDIO はノーコードSaaSのため Origin CA を設置できない可能性が高く、synon.co.jp 版の SSL 章(Full strict + Origin CA + AOP)が成立しない可能性がある。
今回の調査で、この懸念は事実だと確認できました。
項目 実態
STUDIO に他社証明書を設置できるか 実質不可。 そもそも Cloudflare Origin CA はブラウザが信頼しないため、STUDIO 側に置いても意味がありません
STUDIO を Cloudflare のオレンジ雲配下に置けるか 🔴 公式が否定。 "If you use Cloudflare, please disable DNS Proxy mode. When it's enabled, certificate issuance or renewal may not work properly."
正式に CDN を前段に置く手段 「Custom Proxy」アドオンが必要。Business Plus 以上+¥39,800/月
既存資料の書き換え方針
「Step 1 ① DNS移管」の SSL 章は、synon.co.jp については「Workers へ移行後は該当なし」 と書き換えてください。Origin CA / AOP / Full (strict) の議論は、社内システム(EC2)向けの記述としてのみ残す のが正しい姿です。
9.2 🔵 訂正C(www のダングリング)が確定・解消します
既存資料の訂正C (www.synon.co.jp は Xserver を指すが HTTP 応答を確認できなかった → ダングリングの可能性)について、実測で apex(STUDIO)と www(Xserver)が別基盤を指していることが確定しました。 8.3節の手順で apex への 301 に統一されます。
9.3 既存資料のどこを書き換えるか
資料 箇所 書き換え内容
cloudflare-secure-cloud-buildout-operations.md全体の前提 「オリジン: AWS EC2」→ 「Web: Cloudflare Workers(本移行後)/ 社内システム: AWS EC2」 に分離
Step 1 の SSL 設計章 Origin CA / AOP / Full (strict) の議論を「社内システム(EC2 + Tunnel)向け」に限定。 コーポレートサイトは Workers なので該当なし
Step 1 の DNS レコード表 www の行を「Xserver を指す」→ 「apex へ 301」 に変更。* ワイルドカードの行に削除の判断 を追記
運用計画・SPOF 表 「コーポレートサイトのオリジン」の SPOF が STUDIO → Cloudflare に変わる。単一ベンダー依存が上がる点 を明記(10.4節も参照)
運用カレンダー 下記 9.5節を追加
cloudflare-syncsync-jp-buildout.md9.4節 訂正B・C 「本移行により解決済み」と追記
9.4 3つの構成の整理
社内に「Cloudflare の下にあるもの」が3系統になります。混同しないよう整理しておきます。
synon.co.jp(Web) syncsync.jp(Web) 社内システム
オリジン Cloudflare Workers (本移行後)Xserver 共有サーバ AWS EC2
到達方法 公開(誰でも) 公開(誰でも) Zero Trust(WARP + Tunnel)
SSL 設計 ✅ 考慮不要 (Cloudflare 完結) ⚠️ Full (strict) + Let's Encrypt 自動更新の監視が必須 Full (strict) + Origin CA + mTLS
デプロイ GitHub Actions(本書) FTP / 管理画面 既存の手順
最大のリスク ベンダーロックイン(10.4節) 🔴 90日後の 526 シート管理・ログ24時間
メール Google Workspace Xserver 自前 —
この表を既存資料の冒頭に入れると、以後の議論が混乱しなくなります
9.5 運用カレンダーへの追加項目
頻度 タスク 所要 理由
push のたび(自動) GitHub Actions のビルド・検証・デプロイ・スモークテスト 自動 —
月次 Dependabot の Action 更新 PR をマージ 10分 SHA ピン留めの更新(6.6節)
四半期 _redirects の棚卸し15分 不要になった 301 の整理。ただし移行由来のものは3年は残す
四半期 Search Console のインデックス状況確認 15分 —
年次 🔴Cloudflare API トークンのローテーション 30分 TTL 切れで自動デプロイが無言で止まります (6.2節)
年次 構造化データ・OGP・会社情報の内容確認 30分 —
随時 Cloudflare の Workers 関連仕様変更の追従 — Workers は変化が速い。compatibility_date の更新方針を決めておく
API トークンの期限切れが、この構成で最も起きやすい「静かな障害」です
期限の1ヶ月前にカレンダー通知を設定してください。デプロイが失敗してもサイトは動き続ける ため、気づくのが遅れます。
10 未確定事項(着手前に潰すこと)
10.1 🔴 最優先
# 未確定事項 確認方法 影響
1 🔴 DNS 移管(既存資料 Step 1)を実施する意思決定ができているか 経営判断 未実施ならこの移行は着手できません (3章)
2 🔴 連番URL 5本(/1 /service-1 /development-1 /work-1 /company-1)の中身は何か ブラウザで開く 移行対象ページ数と 301 設計が決まります (4.3・8.1節)
3 🔴 topics と posts の使い分けは何か STUDIO の CMS ダッシュボードを確認 URL を1本化するか否か (8.1節)
4 🔴 /contact のフォームはどこに送信されているか (STUDIO Form か外部か) STUDIO の設定を確認 代替実装の要否と方式 (4.7節)
10.2 ⚠️ 重要
# 未確定事項 確認方法 / 影響
5 現在の STUDIO のプラン(Personal / Business / Business Plus) 請求情報。削減額の算定に必要
6 サイトのデザインを誰が作ったか(社内 / 外注)、元データ(Figma 等)が手元にあるか あれば作り直しが大幅に楽になります
7 有償フォントを使っていないか 使っていれば移行先でのライセンス確認が必要
8 Google Analytics / タグマネージャの設置状況 移植漏れを防ぐ
9 外部からの被リンクが多いページはどこか Search Console / 外部ツール。URL 変更の判断材料 (8.1節)
10 *.synon.co.jp の実在サブドメインは www だけかXserver・STUDIO の設定、CT ログ(crt.sh)
10.3 📋 技術的な要検証事項(実装時に確認)
# 項目 状況
11 wrangler rollback の非対話フラグ(--yes 等)の正確な名称公式ドキュメントで確認できず。wrangler rollback --help で確認
12 cloudflare/wrangler-action の v4 最新パッチとコミットSHAリリースページで都度確認(6.6節)
13 Zone > Workers Routes > Edit 権限が本当に必要か付けずに試し、エラーが出たら追加
14 静的アセットの合計サイズ上限 公式に明記なし。20ページ規模では問題にならない
15 Redirect Rules の Free プラン上限件数 公式の Availability ページで確認
16 WebMCP の宣言的API(<form> への属性注釈)の正確な構文 Chrome の該当ドキュメントで確認(2.5節)
17 MCP のディスカバリ規約(.well-known/mcp/server-cards.json、SEP-2127)の帰趨 Draft 段階。現時点では設置不要
10.4 経営判断が必要な論点
ベンダーロックインが1段階深まります
移行後、synon.co.jp は DNS・CDN・WAF・ホスティングのすべてが Cloudflare になります。既存資料に記載のとおり、Free プランには SLA もテクニカルサポートもありません (2025/11/18 に約6時間の全面障害の実績あり)。
緩和策 内容
✅ 出口が確保されている ソースコードは GitHub にあり、dist/ は単なる静的ファイル です。他社(Netlify / S3 / Xserver)へ数時間で移せます。 これが「素の HTML/CSS/JS」を選んだ最大の利点です。STUDIO のときと違い、逃げ道があります
✅ break-glass Cloudflare 障害時の経路は既存資料で整備済み
⚠️ DNS も Cloudflare にある Cloudflare の DNS が落ちると移設先を指すこともできません。レジストラ(ドメイン管理)は Cloudflare 以外に置いておく ことを推奨します
11 まとめ
この文書の要点
移行対象は実測で全20ページ 素の HTML/CSS/JS で作り直す判断は、この規模なら妥当です。
🔴 DNS 移管が絶対の前提条件 Workers のカスタムドメインは Cloudflare DNS 上のゾーンでしか設定できません。既存資料の「① DNS移管」を先に完了させてください。
「WebMCP のために Workers」は成立しませんが、結論は変えなくてよい 理由を「Cloudflare 公式の推奨」「リモート MCP サーバーの同居」「将来の動的機能」に差し替えてください。
🔴 STUDIO はコードもCMSデータも書き出せません 画像とテキスト(自社の著作物)を回収し、デザインは DevTools で実測して書き直す のが正しい進め方です。
共通パーツ問題は60行のビルドスクリプトで解決します フレームワークは不要です。
切替は www → apex の順に 本番に影響を与えずに実ドメインで検証できます。
🔴 STUDIO の解約は移行の1ヶ月後 解約した瞬間にサイトが非公開になり、切り戻し先が消えます。
🔵 この移行で、既存資料の訂正B(Origin CA 問題)が根本解決します オリジンが Cloudflare 自身になるためです。
🔴 最も起きやすい静かな障害は「API トークンの期限切れ」 サイトは動き続けるので気づきません。カレンダーに入れてください。
着手順序
【今すぐできること(DNS 移管を待たずに)】
□ 20ページをブラウザで開き、棚卸し表を作る(10.1 の #2 #3 #4 を潰す)
□ 画像アセットを全部ダウンロードする
□ フォーム回答を CSV エクスポートする
□ Search Console でベースラインを取る
□ GitHub リポジトリを作り、design token を決めてトップページだけ作ってみる
→ *.workers.dev で表示できるところまでは、DNS 移管なしで到達できます
【DNS 移管後】
□ Workers にカスタムドメインを設定
□ www で先行検証 → apex を切替
□ 1ヶ月観察 → STUDIO 解約
【移行完了後・別フェーズ】
□ WebMCP の実装(Chrome Origin Trial の動向を見て判断)
□ リモート MCP サーバー(/mcp)の追加
関連文書
文書 内容
cloudflare-secure-cloud-buildout-operations.md / .htmlsynon.co.jp 版・構築手順書+運用計画書。 Zero Trust、DNS移管(① Step 1)、Gateway、DEX、Turnstile、運用計画の本体。本書の前提条件はこの Step 1
cloudflare-syncsync-jp-buildout.md / .htmlsyncsync.jp 版・Xserver 編。 2ドメイン比較。本書 9.3節で訂正B・Cの解決を反映
studio-to-cloudflare-workers-migration.md / .html(本書)STUDIO → Workers 移行 + GitHub Actions 自動デプロイ
synon.co.jp を STUDIO から Cloudflare Workers へ移行する + GitHub Actions による push 自動デプロイ
作成日 2026-08-19 / DNS・sitemap の実測値は同日時点のものです
10章の未確定事項(特に #1〜#4)を潰してから着手してください