SYNON 記事 Cloudflare・インフラ

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 / 月

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      なし
20
移行対象URL(静的16 + CMS 4)
¥0
移行後の月額(Workers Free)
〜¥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: topics2/topics/dify(2025-01-11) /topics/info(2025-01-10)
CMS: posts2/posts/zsFXXiMM(2026-01-07) /posts/renew(2025-01-10)

実測して気づいた4つのこと

#気づき意味
1連番URLが5つある/1 /service-1 /development-1 /work-1 /company-1STUDIO でページを複製した痕跡と考えられます。このまま20ページ全部を作り直すのではなく、まず「本当に必要なページか」を棚卸ししてください。重複コンテンツは SEO 上もマイナスです(4.3節)
2STUDIO CMS を2種類使っているtopicsposts「CMS 不要」という方針はコンテンツが4件しかないので成立します。ただし今後お知らせを追加するたびに開発者が HTML を書く運用になります。この点だけは合意を取っておいてください(4.5節)
3www が apex と別のサーバー(Xserver)を指している既存資料で「ダングリングの可能性」として挙げた項目が確定しました。本移行はこれを直す好機です(9.2節)
4ワイルドカード * が存在する存在しないサブドメインもすべて STUDIO の IP を返します。移行時に明示レコードへ置き換える判断ができます(8.3節)

この移行で同時に解決すること

サイトの移行と同時に、3つの積み残しが片付く いま抱えている問題 移行後 STUDIO に Origin CA を設置できない 公式が「Cloudflare の DNS Proxy を無効に」と明記 → apex をオレンジ雲にできない=WAF もキャッシュも効かない オリジンが Cloudflare 自身になる Origin CA も、オリジン証明書の更新監視も、 526 エラーの心配も、すべて不要になる www が Xserver を指したまま放置 apex(STUDIO)と別基盤。ダングリング状態 apex への 301 に統一 既存の被リンク・ブックマークを拾える STUDIO の月額費用 20ページ・CMS 2種なので Personal 以上(¥1,720/月〜) Workers Free で ¥0 年間 約 ¥21,000〜¥65,000 の削減 静的アセットへのリクエストは Workers Free で「課金対象外かつ無制限」。20ページのコーポレートサイトは永久に ¥0 で運用できる
図1: 移行で同時に解決する3つの問題。特に1つ目は既存資料の「訂正B」を根本解決する

着手前に必ず理解しておくこと

#事実出典
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 レコード

現状:Web は STUDIO、メールは Google、www だけ Xserver に取り残されている 権威DNS ns1〜ns5.xserver.jp レコード A @ → 203.0.113.10 A * → 203.0.113.10 A www → 198.51.100.10 MX Google Workspace ×5 TXT SPF / DKIM / DMARC CAA / AAAA / DS なし DNSSEC 未設定 STUDIO(Google Cloud) 203.0.113.10 ← 全ユーザー共通の共有IP Xserver 198.51.100.10 ← www だけがここを向いている Google Workspace MX は apex A と独立 → 移行してもメールは無傷 NS が Cloudflare でないことが、この移行の最大の前提条件(3章)
図2: synon.co.jp の現状。MX が apex A と独立しているため、syncsync.jp と違って apex を安全に切り替えられる
$ 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.jpsyncsync.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🔍 要確認(テストページの可能性)
URLlastmodコレクション
/topics/dify2025-01-11topics
/topics/info2025-01-10topics
/posts/zsFXXiMM2026-01-07posts
/posts/renew2025-01-10posts
2つの注意点
  • /posts/zsFXXiMM はランダム文字列のスラッグです。STUDIO が自動採番したものと考えられます。新サイトで意味のある URL に変えたい場合は、必ず旧URLから 301 を張ってください(8.1節)
  • topicsposts が別コレクションとして併存しています。役割の違いを確認し、新サイトでは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 はブラウザの中で完結する ── サーバーには届かない ユーザーのブラウザ synon.co.jp のページ <script> document.modelContext .registerTool({ ... }) </script> ブラウザ内の AI エージェント 拡張機能 / エージェント型ブラウザ HTML/JS/CSS を配信するだけ ホスティング(何でもよい) Cloudflare Workers / Pages / Netlify / S3 — WebMCP の動作に一切影響しない →「WebMCP 対応のために Workers が必要」という因果関係は成立しない
図3: WebMCP の実行モデル。同じ HTML を Pages で配信しても Netlify で配信しても、まったく同じように動作する
社内説明としては訂正が必要です 「WebMCP 対応のために Workers が必要」と説明したまま進めると、後から「なぜ Workers なのか」を説明できなくなります。

2.2 それでも Workers が正解である3つの理由

結論(Workers を選ぶ)は変えなくて構いません。理由を差し替えれば済みます。

結論は同じ。理由を差し替える ✕ これまでの説明 「サイトが WebMCP に対応しているので それに合う Workers にしたい」 ○ 言い直した説明 「Cloudflare 公式が新規に推奨しており、 将来 /mcp を同居させたいので Workers」 理由① 公式の推奨 Cloudflare 公式ブログ(2025-04): "you should start with Workers" "all of our investment ... will be dedicated to improving Workers" → Pages は事実上メンテナンスモード 理由② /mcp を同居できる synon.co.jp/mcp にリモート MCP サーバーを立て、Claude Desktop 等 から接続させる構成 Pages Functions では Durable Objects を定義できない(二段構えが必要) 理由③ 将来の動的機能 ・問い合わせフォームの受信処理 ・A/Bテスト、認証付きページ ・Cron Triggers ・Workers Logs / Tail Workers ・Source Maps いずれも Workers にしかない
図4: 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 MiB25 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.jsonSEP-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 にカスタムドメインを割り当てられません。

作業の依存関係 ── ①が終わらないと②③に着手できない ① DNS を Cloudflare へ移管 NS を Xserver → Cloudflare(既存資料「構築手順書」Step 1) ② Workers にカスタムドメインを設定 本書 5.4節 ③ GitHub Actions で自動デプロイ 本書 6章 DNS 移管を待たずに できること サイトの構築・レビューは *.workers.dev で先行できる 朗報 synon.co.jp の DNS 移管は syncsync.jp より簡単 (MX が独立しているため)
図5: 作業の依存関係。①は既存資料の Step 1 そのもので、本書はその完了を前提にしている

3.2 synon.co.jp の DNS 移管が簡単な理由

論点synon.co.jpsyncsync.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 推奨する進め方 — 「著作物」と「生成物」を分けて考える

STUDIO から「持ち出せるもの」と「作り直すもの」を切り分ける 🟢 自社の著作物 ── そのまま回収してよい 画像・写真・ロゴ・PDF DevTools > Network > Img でフィルタして URL を列挙し全回収 本文テキスト・見出し・キャッチコピー 20ページなので、ブラウザで開いてコピーで十分 デザインの意図(配色・余白・タイポグラフィ) DevTools で「測る」のは問題ない。自分の CSS として書き直す 🔴 OGP 画像も忘れずに storage.googleapis.com にあり、解約で消える可能性がある 🔴 STUDIO の生成物 ── コピーしない 生成された HTML の構造・クラス名 利用規約 第13条3項「複製・改変・翻案の禁止」に触れうる そもそも自動生成クラスで可読性が低く、保守できない 生成された CSS 同上。流用すると「読めない CSS」を抱えたまま 移行することになり、移行の意味が薄れる 🟡 フォント Google Fonts なら自由。有償フォントの場合は 移行先でもライセンスが有効か要確認
図6: 移行時の切り分け。自社の著作物は回収し、STUDIO の生成物は参照するにとどめて書き直す

実務的な手順

全20ページのフルページスクリーンショットを撮るPC幅・タブレット幅・スマホ幅の3種類。これが「デザイン仕様書」の代わりになります。
DevTools で design token を実測する色(Computed > color / background-color)、タイポグラフィ(font-family / font-size / line-height / font-weight)、余白のスケール、ブレークポイント。
トークンを CSS カスタムプロパティにまとめる下記の例を参照。
共通パーツ(header / footer)を先に作り、トップページを完成させる
残り19ページを流し込む
/* 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.txtSitemap: 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>
addresssameAs は実際の値に置き換えてください 空のまま出すくらいなら項目ごと削除するほうが良いです。記事ページには BlogPosting、パンくずには BreadcrumbList を足せます。

OGP 画像

現行の og:image URL から画像をダウンロード
src/assets/img/ogp.png として配置(推奨サイズ 1200×630)
partials/head.htmlog:imagehttps://synon.co.jp/assets/img/ogp.png に変更
移行後に検証:X (Twitter) Card Validator / Facebook Sharing Debugger(キャッシュの再取得も実施)/ Slack に URL を貼ってプレビュー確認

4.7 /contact フォームの扱い(参考 — 本書のスコープ外だが必須事項)

本書の章立てには含めていませんが、/contact が実在する以上、避けて通れません STUDIO Form は移行と同時に失われます。まず回答データを CSV でエクスポートしてください(回答一覧画面右上の3点メニューから可能)。
内容費用評価
AWorkers + Resend 等の外部メールAPIWorkers Free + Resend 無料枠推奨。Cloudflare 公式チュートリアルあり
BWorkers + Cloudflare Email Sending🔴 Workers 有料プラン限定($5/月〜)⚠️ 有料化が必要
C外部フォームSaaS(Formspree 等)サービス次第○ 実装は最小
DGoogle 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 整理を先に実施してください。

5Cloudflare 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.html200 OK で返す❌ SPA ではない

dist/404.html を必ず用意してください。用意しないと存在しない URL で素っ気ない画面が出ます。

html_handling

挙動
auto-trailing-slash(既定・推奨)foo.html/foofoo/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/difysrc/pages/topics/dify.html に置けば /topics/dify になります(topics/dify/index.html にすると /topics/dify/ になってしまうので注意)。

run_worker_first

"run_worker_first": ["/mcp", "/mcp/*", "/api/*", "!/api/docs/*"]
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 dev8787番です(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ページのコーポレートサイトで問題になる規模ではありませんが、動画などの大容量ファイルを大量に置く場合は事前に確認してください。

6GitHub Actions で自動デプロイ

6.1 全体像

PR ではプレビュー、main では本番 ── 失敗したら自動でロールバック 開発者 GitHub Actions Cloudflare feature ブランチ git push → PR 作成 preview.yml npm ci → npm run build wrangler versions upload --preview-alias pr-123 PR にプレビューURLをコメント プレビュー環境 pr-123-synon-corporate-site .xxx.workers.dev レビュー・承認 → main へマージ main へマージ または手動実行 deploy.yml ① build: npm ci → build → HTML検証 → リンク切れ ② Environment "production" の承認待ち(任意) ③ wrangler deploy ④ スモークテスト(本番URLを curl して 200 を確認) 本番 https://synon.co.jp/ ④ が失敗したら wrangler rollback で自動巻き戻し デプロイが成功しても中身が壊れていれば意味がない。「本番URLを叩いて 200 が返る」ところまで自動で確認するのが要点
図7: CI/CD の全体像。承認フローとスモークテスト付きロールバックを入れられるのが GitHub Actions を選ぶ理由

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 secretsEnvironment 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 URLwrangler deploy / versions upload自動生成バージョンごとの固定URL
Aliased Preview URLwrangler 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 がこれ。シークレットが渡らないので、書かないとジョブが不可解に失敗します
3permissions: を最小にワークフロー冒頭で contents: read。書き込みが要るジョブだけ個別に追加
4リポジトリ既定を read-only にSettings > Actions > General > Workflow permissionsRead 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.ymlpackage-ecosystem: "github-actions" を追加してください。

6.7 なぜ Workers Builds(Cloudflare の Git 連携)ではなく GitHub Actions か

項目Workers BuildsGitHub Actions(本書の採用)
セットアップ✅ 最小限(リポジトリを繋ぐだけ)⚠️ workflow を書く必要がある
ビルド分数Free 3,000分/月、同時ビルド 1Free 2,000分/月(プライベート)、パブリックは無制限
ビルドタイムアウト20分6時間
デプロイ前の検証⚠️ ビルドコマンド内に押し込む形になるジョブとして分離でき、失敗時に止められる
承認フローGitHub Environments の required reviewers
失敗時の自動ロールバック✅ 実装できる
他の CI と統合
本件で GitHub Actions を選ぶ理由 「push したら出る」だけなら Workers Builds で十分ですが、コーポレートサイトは「間違った内容が出ること」のコストが高いため、①デプロイ前の検証 ②本番デプロイの承認 ③スモークテスト失敗時の自動ロールバック、の3つを入れられる GitHub Actions を採用します。
両方を同時に有効にしないでください 二重デプロイになり、どちらが反映されたか分からなくなります。GitHub Actions を使うなら、Cloudflare 側の Git 連携は接続しないこと。

7DNS 切替とロールバック

7.1 切替前チェックリスト

すべてにチェックが付くまで切り替えないでください。

新サイト側

Cloudflare 側 / DNS 側 / STUDIO 側

7.2 切替手順

この移行の良いところ 「apex A レコードを1本差し替えるだけ」で完了します。MX が独立しているため、メールへの影響を考える必要がありません。
www を先に切り替える ── 本番に影響を与えずに実ドメインで検証できる Step 1 最終デプロイ main へマージ → deploy.yml スモークテスト通過を確認 apex: STUDIO のまま www : Xserver のまま Step 2 www を切替 ★ Custom Domain に www を追加 既存の www A を削除 apex: STUDIO のまま www : 新サイト ← ここで検証 Step 3 30分〜1時間の確認 ・全20ページの表示 ・リンク切れがない ・フォーム送信 ・404 の挙動 本番URLはまだ無傷 Step 4 apex を切替 Custom Domain に apex を追加 A 203.0.113.10 を削除 ワイルドカード * も削除 apex: 新サイト(本番完了) Step 5 切替直後10分以内の確認(省略しないこと) □ https://synon.co.jp/ が新サイト □ https://www.synon.co.jp/ が apex へ 301 □ 旧URL(/1 /service-1 等)が 301 で正しく飛ぶ □ /sitemap.xml /robots.txt が返る □ Gmail から info@synon.co.jp へ送信 → 着信 □ info@synon.co.jp から外部へ送信 → 着信 切り戻しは3段階。上ほど速い ① 内容の問題 → wrangler rollback 即時 ② 全体をやめて STUDIO へ → Custom Domain 削除 + A 203.0.113.10 再作成 数分(TTL 300秒) ③ DNS 移管そのものの問題 → NS を Xserver へ戻す(最大24時間)※3章でフェーズを分けている理由
図8: 切替手順。Step 2 で www を先に切り替えることで、本番に影響なく実ドメイン・実証明書で検証できる
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秒)
3DNS 移管そのものに問題がある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 側の独自ドメイン設定を解除(公開パネルのドメイン欄の [×])

8SEO ・ 既存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/1301 → / または 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.xml301Search 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 のみと判明しているので安全
BWorkers へ向ける⚠️ 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.xml301 → /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 で一元管理
OGPog: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/月
オリジンが Cloudflare 自身になることで、SSL の論点が丸ごと消える 移行前 ブラウザ Cloudflare オレンジ雲にできない STUDIO 203.0.113.10(共有IP) Origin CA 設置不可 / Full (strict) 不成立 / WAF もキャッシュも効かない 移行後 ブラウザ Cloudflare Workers オリジンが Cloudflare 自身 Universal SSL Origin CA も、オリジン証明書の更新監視も、526 エラーの心配も、すべて不要 syncsync.jp 版で最大のリスクだった論点が、synon.co.jp では丸ごと消える
図9: SSL 設計の解消。オリジン側の証明書という概念自体が無くなるのが Workers 移行の副次的な最大メリット
既存資料の書き換え方針 「Step 1 ① DNS移管」の SSL 章は、synon.co.jp については「Workers へ移行後は該当なし」と書き換えてください。Origin CA / AOP / Full (strict) の議論は、社内システム(EC2)向けの記述としてのみ残すのが正しい姿です。

9.2 🔵 訂正C(www のダングリング)が確定・解消します

既存資料の訂正Cwww.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 WorkspaceXserver 自前
この表を既存資料の冒頭に入れると、以後の議論が混乱しなくなります

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🔴 topicsposts の使い分けは何かSTUDIO の CMS ダッシュボードを確認URL を1本化するか否か(8.1節)
4🔴 /contact のフォームはどこに送信されているか(STUDIO Form か外部か)STUDIO の設定を確認代替実装の要否と方式(4.7節)

10.2 ⚠️ 重要

#未確定事項確認方法 / 影響
5現在の STUDIO のプラン(Personal / Business / Business Plus)請求情報。削減額の算定に必要
6サイトのデザインを誰が作ったか(社内 / 外注)、元データ(Figma 等)が手元にあるかあれば作り直しが大幅に楽になります
7有償フォントを使っていないか使っていれば移行先でのライセンス確認が必要
8Google Analytics / タグマネージャの設置状況移植漏れを防ぐ
9外部からの被リンクが多いページはどこかSearch Console / 外部ツール。URL 変更の判断材料(8.1節)
10*.synon.co.jp の実在サブドメインは www だけかXserver・STUDIO の設定、CT ログ(crt.sh

10.3 📋 技術的な要検証事項(実装時に確認)

#項目状況
11wrangler rollback の非対話フラグ(--yes 等)の正確な名称公式ドキュメントで確認できず。wrangler rollback --help で確認
12cloudflare/wrangler-action の v4 最新パッチとコミットSHAリリースページで都度確認(6.6節)
13Zone > Workers Routes > Edit 権限が本当に必要か付けずに試し、エラーが出たら追加
14静的アセットの合計サイズ上限公式に明記なし。20ページ規模では問題にならない
15Redirect Rules の Free プラン上限件数公式の Availability ページで確認
16WebMCP の宣言的API(<form> への属性注釈)の正確な構文Chrome の該当ドキュメントで確認(2.5節)
17MCP のディスカバリ規約(.well-known/mcp/server-cards.json、SEP-2127)の帰趨Draft 段階。現時点では設置不要

10.4 経営判断が必要な論点

ベンダーロックインが1段階深まります 移行後、synon.co.jpDNS・CDN・WAF・ホスティングのすべてが Cloudflare になります。既存資料に記載のとおり、Free プランには SLA もテクニカルサポートもありません(2025/11/18 に約6時間の全面障害の実績あり)。
緩和策内容
出口が確保されているソースコードは GitHub にあり、dist/ は単なる静的ファイルです。他社(Netlify / S3 / Xserver)へ数時間で移せます。これが「素の HTML/CSS/JS」を選んだ最大の利点です。STUDIO のときと違い、逃げ道があります
✅ break-glassCloudflare 障害時の経路は既存資料で整備済み
⚠️ DNS も Cloudflare にあるCloudflare の DNS が落ちると移設先を指すこともできません。レジストラ(ドメイン管理)は Cloudflare 以外に置いておくことを推奨します

11まとめ

この文書の要点

移行対象は実測で全20ページ素の HTML/CSS/JS で作り直す判断は、この規模なら妥当です。
🔴 DNS 移管が絶対の前提条件Workers のカスタムドメインは Cloudflare DNS 上のゾーンでしか設定できません。既存資料の「① DNS移管」を先に完了させてください。
「WebMCP のために Workers」は成立しませんが、結論は変えなくてよい理由を「Cloudflare 公式の推奨」「リモート MCP サーバーの同居」「将来の動的機能」に差し替えてください。
🔴 STUDIO はコードもCMSデータも書き出せません画像とテキスト(自社の著作物)を回収し、デザインは DevTools で実測して書き直すのが正しい進め方です。
共通パーツ問題は60行のビルドスクリプトで解決しますフレームワークは不要です。
切替は wwwapex の順に本番に影響を与えずに実ドメインで検証できます。
🔴 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 自動デプロイ