# 中継とホスティング

> Cloudflare Pages の中継（Pages Functions）で配信前に値を埋め込む方法と、それ以外のホスティングでの使い方。

URL: https://sitekit.example/docs/hosting

## 2つのやり方

CMS の値を HTML に入れるやり方は2つあります。フォームと流入元の記録は、どちらでも script だけで動きます。

| | Cloudflare Pages の中継（おすすめ） | `data-embed`（ほかのホスティング） |
|---|---|---|
| 値が入るとき | 配信の前（サーバー側） | 表示されたあと（ブラウザ側） |
| 検索エンジン・JavaScript 無効の人 | 値の入ったページが届く | 静的な文言が拾われることがある |
| 表示のチラつき | 無い | 一瞬、静的な文言が見える |
| 記事ごとのページ・タグページ・記事の `url` | 使える | 使えない |
| 管理画面の GTM ID の差し込み | 使える | 使えない（`data-gtm` を使う） |

## Cloudflare Pages の中継

### 置くファイル

サイトの `functions/` に中継のファイルを2つ置きます。

```text
functions/_middleware.ts
functions/_cms/render.ts
```

- `_cms/render.ts` は生成物です。編集しません（編集すると、更新のときに食い違います）
- 外部向けの配布の手段（ダウンロード・CLI）は準備中です （近日）

### 環境変数

Pages プロジェクトの `vars`（`wrangler.jsonc` か Cloudflare のダッシュボード）で設定します。

| 変数 | 必須 | 説明 |
|---|---|---|
| `CMS_SITE` | 必須 | サイト ID |
| `CMS_ORIGIN` | 必須 | `https://cdn.sitekit.example`（末尾のスラッシュは不要） |
| `CMS_LOCALES` | | `言語:接頭辞` のカンマ区切り。省略すると `ja:/` |
| `CMS_NEWS_PATH` | | 記事ページのパス兼コレクションのキー。省略すると `news` |
| `CMS_INDEXABLE_HOSTS` | | 検索に載せてよいホスト名のカンマ区切り。省略すると何もしない |

`CMS_SITE` か `CMS_ORIGIN` が無いと、中継は何もせず静的な HTML をそのまま返します。

```jsonc
{
  "name": "hanaya",
  "compatibility_date": "2026-08-01",
  "pages_build_output_dir": "public",
  "vars": {
    "CMS_SITE": "hanaya",
    "CMS_ORIGIN": "https://cdn.sitekit.example",
    "CMS_LOCALES": "ja:/,en:/en",
    "CMS_NEWS_PATH": "news"
  }
}
```

### 言語の判定

`CMS_LOCALES` は `ja:/,en:/en,zh-tw:/zh-tw` のように書きます。先頭が主言語です。パスの**最長一致**で言語が決まります。

| パス | 言語 |
|---|---|
| `/`, `/about/` | `ja`（知らないパスもここに落ちる） |
| `/en`, `/en/`, `/en/about`, `/en.html` | `en` |
| `/english` | `ja`（接頭辞で始まるだけの別ページは一致しない） |
| `/zh-tw/news/x` | `zh-tw` |

`Accept-Language` は見ません。同じ URL は誰が見ても同じ言語です。

### 検索避け（`CMS_INDEXABLE_HOSTS`）

プレビュー用のホスト（`*.pages.dev` など）と本番のホストで同じ中身を配ると、重複したページとして扱われ、プレビューのほうが検索結果に出ることがあります。`CMS_INDEXABLE_HOSTS` に**本番のホストだけ**を書くと、それ以外のホストで返す HTML に `X-Robots-Tag: noindex` が付きます。

```jsonc
"CMS_INDEXABLE_HOSTS": "www.example.jp,example.jp"
```

| 値 | 意味 |
|---|---|
| 未設定・空 | 何もしない |
| `www.example.jp,example.jp` | この2つ以外のホストの HTML に `noindex` |
| `-` | どのホストも検索に載せない（本番のドメインがまだ無いとき） |

- 完全一致です（`www.example.jp` を書いても `a.www.example.jp` は許可されない）。大文字・小文字は区別しません
- 付くのは HTML だけです（画像や CSS には付けない）
- サイト側が `_headers` などで `X-Robots-Tag` を出している場合は上書きしません

### 反映までの時間

公開 JSON は 60 秒キャッシュされ、中継が返す HTML も `max-age=60` です。お店の人が「公開」を押してから**最長1分**で全ページに反映されます。サイトの再デプロイは要りません。

### CMS が止まっているとき

**静的な HTML をそのまま返します。** 取得の失敗・タイムアウト（5秒）・壊れた JSON のどれでも、埋め込みをしなかったページを返すだけで、サイトは止まりません。

例外は記事ページだけです。取得に成功したうえで ID が見つからないときは 404 を返します。取得できなかったときは雛形をそのまま返します。

### 動いているかの確かめ方

中継が値を埋め込むと、レスポンスヘッダに `x-sitekit-rendered: 1` が付きます。**GET で確かめます。`curl -I`（HEAD）では付きません。**

```bash
curl -sD - -o /dev/null https://example.com/ | grep -i sitekit
curl -s https://example.com/ | grep -o 'data-sk-rendered="1"'
```

付いていなければ、環境変数の設定漏れか、CMS に届いていないか、そのページが HTML（200）ではありません。

## ほかのホスティング（`data-embed`）

Cloudflare Pages 以外（関数を動かせない静的ホスティングなど）では、script に `data-embed` を付けます。

```html
<script src="https://cdn.sitekit.example/sitekit.js" data-site="hanaya" data-embed defer></script>
```

- 表示のあとに公開 JSON を取得して、`data-cms` 系の印に値を入れます
- **`document.body` の中身を丸ごと置き換えます。** body の要素に付けたイベントは消えます（フォームは自動で付け直します）。サイト独自の JavaScript がある場合は避けてください
- 取得に失敗したときは何もしません。静的な HTML が残ります
- 記事ごとのページ（`/news/<id>`）・タグページ・記事の `url` は中継の機能なので使えません

> 中継を使うサイトでは **`data-embed` を付けません。** 埋め込みは2回実行できません（1回目の値を2回目が上書きします）。中継済みのページには `data-sk-rendered="1"` が付いていて script 側は何もしませんが、そもそも併用しないでください。`data-embed` の無い script は中継と併用します（フォームと流入元の記録に要ります）。
