考え方
HTML の要素に data-cms 系の属性を付けると、その箇所が管理画面から直せるようになります。何を直せるかは schema.json で宣言します。
- 要素の中に書いた文言は既定の文言です。値が無いとき(未入力、または CMS に届かないとき)は、HTML がそのまま残ります
- 値は文字としてエスケープされて入ります。お店の人の入力で HTML やスクリプトが差し込まれることはありません
- 値の埋め込みは、Cloudflare Pages の中継(配信前)か
data-embed(表示時)が行います(中継とホスティング)
印の一覧
| 属性 | 働き |
|---|---|
data-cms="key" | 要素の中の文字を置き換える |
data-cms-html="key" | 同じく置き換え、改行を <br> にする |
data-cms-src="key" | img / video / source の src を置き換える |
data-cms-attr="属性:key" | 任意の属性を置き換える(例: content:title、href:url) |
data-cms-blocks="key" | 記事本文(ブロックの並び)を中に描く |
data-cms-hide-empty | 値が空文字のとき要素ごと消す(data-cms / data-cms-html だけに効く) |
<template data-cms-list="k" data-cms-limit="n"> | 一覧の1件ごとに中身を繰り返す(n 件まで) |
<template data-cms-list="k" data-cms-filter="f=v"> | 条件に合う件だけ繰り返す |
<template data-cms-empty="k"> | 一覧が空のときだけ中身を出す |
<template data-cms-entry="k"> | 記事ページで、その記事1件を描く |
<template data-cms-each="k"> | 配列(タグなど)の要素ごとに繰り返す |
data-cms-fallback="k" | 一覧 k が届いたら要素ごと消す(届かないときの代わりの表示) |
テキスト
グループ(schema.json の groups)の値は <グループ>.<項目> で指定します。
<dd data-cms="hours.days">月〜土</dd>
<!-- 改行を <br> に。空にすると枠ごと消える -->
<p class="notice" data-cms-html="hours.notice" data-cms-hide-empty>臨時休業のお知らせはありません。</p>写真
一覧や記事の写真(type: "image" の項目)は、絶対 URL で届きます。data-cms-src で src に入れます。
<template data-cms-list="news">
<article>
<img data-cms-src="thumbnail" src="/img/placeholder.jpg" alt="">
<h3 data-cms="title">記事タイトル</h3>
</article>
</template>href や src などの URL を入れる属性には、javascript: / data: / vbscript: で始まる値は入りません(既定の値が残ります)。data-cms-attr で on… のイベント属性・srcdoc・formaction は書き換えられません。
お知らせなどの一覧
コレクション(schema.json の collections)は <template data-cms-list> で繰り返します。中の data-cms には、その1件の項目名を書きます。
<ul class="news">
<template data-cms-list="news" data-cms-limit="3">
<li>
<time data-cms="date">2026-01-01</time>
<a data-cms-attr="href:url" href="/news/"><span data-cms="title">記事タイトル</span></a>
</li>
</template>
<!-- 1件も無いときだけ出る -->
<template data-cms-empty="news">
<li>お知らせはまだありません。</li>
</template>
<!-- 一覧が届いたら消える。CMS に届かないときはこれが残る -->
<li data-cms-fallback="news"><span>ホームページを公開しました</span></li>
</ul>- 一覧の順番は schema.json の
sort(例:"date desc")。無ければ管理画面で並べた順です - 1つの一覧に載るのは新しい順に最大 100 件です
- 記事へのリンクは
data-cms-attr="href:url"で張ります。urlは schema.json には無く、中継が記事ごとに/news/<id>(言語の接頭辞つき、絶対 URL)を足したものです。data-embedでは付きません data-cms-limitは整数だけ("3")。それ以外は制限なしとして全件出ます
記事ごとのページ
記事ごとの URL(/news/<id>)は、中継が news/_entry.html を雛形にして描きます。ファイルを記事の数だけ置く必要はありません。Cloudflare Pages の中継を使うサイトだけの機能です。
public/news/_entry.html 記事ページの雛形
public/news/_404.html 存在しない ID のときのページ(任意)
public/en/news/_entry.html 言語ごとに用意する<template data-cms-entry="news">
<h1 data-cms="title">記事タイトル</h1>
<time data-cms="date">2026-01-01</time>
<div data-cms-blocks="body"></div>
</template>- URL は
/news/<id>または/<言語>/news/<id>。<id>は英小文字と数字だけ・32 文字以内 - 公開中の記事に無い ID は 404 になります(
news/_404.htmlがあればそれを返す) - パスとコレクションのキーは中継の環境変数
CMS_NEWS_PATH(省略時news)
<title> と OGP は <head> にも template を置く
data-cms-entry の「その記事1件」として読めるのは template の内側だけです。<head> に直接 <title data-cms="title"> と書くと、記事ではなくサイト共通の値を読みにいきます。画面には出ないので、SNS に貼ったときに初めて気づきます。
<head>
<template data-cms-entry="news">
<title data-cms="title">お知らせ|店名</title>
<meta name="description" data-cms-attr="content:lead" content="店名からのお知らせ。">
<meta property="og:title" data-cms-attr="content:title" content="お知らせ|店名">
<meta property="og:image" data-cms-attr="content:thumbnail" content="../assets/hero.jpg">
<meta property="og:url" data-cms-attr="content:url" content="">
<link rel="canonical" data-cms-attr="href:url" href="">
</template>
<!-- CMS に届かず template が展開されなかったときに残る題 -->
<title data-cms-fallback="news">お知らせ|店名</title>
</head>data-cms-fallback を付けた <title> は、template が展開されると消えます。これが無いと <title> が2つ出ます。記事の url は絶対 URL なので、og:url と canonical にそのまま入れられます。
記事本文(ブロック)
type: "blocks" の項目は data-cms-blocks で描きます。出てくる要素のクラスはすべて sk- で始まり、CSS はサイト側で当てます(スタイルシートは配りません)。
.sk-h { font-size:1.05rem; margin:2rem 0 .5rem; } /* 見出し */
.sk-p { margin:0 0 1rem; } /* 文章 */
.sk-q { border-left:3px solid #e5e7eb; padding-left:1rem; color:#6b7280; }
.sk-ul, .sk-ol { margin:0 0 1rem; padding-left:1.4rem; }
.sk-img img { width:100%; height:auto; }
.sk-img figcaption { color:#6b7280; font-size:.8rem; }
.sk-video { width:100%; }
.sk-embed { position:relative; padding-top:56.25%; }
.sk-embed iframe { position:absolute; inset:0; width:100%; height:100%; border:0; }
.sk-hr { border:0; border-top:1px solid #e5e7eb; }
.sk-pdf, .sk-card, .sk-video-link { display:inline-block; }
/* 文字色。クラス名は変えない。色の値はサイトの配色に合わせてよい */
.sk-c-red { color:#c0392b; }
.sk-c-gold { color:#b8860b; }
.sk-c-gray { color:#6b7280; }
.sk-c-black { color:#111827; }
.sk-c-green { color:#15803d; }
.sk-c-blue { color:#1d4ed8; }お店の人は本文の中で次の書き方が使えます。
| 書き方 | 出力 |
|---|---|
**太字** | <strong> |
[文字](https://…) | 新しいタブで開くリンク(http/https だけ) |
{color:red}文字{/color} | <span class="sk-c-red">(色は red / gold / gray / black / green / blue の6つ) |
https://… をそのまま | 自動でリンク |
.sk-c-* を書き忘れると、色の指定をしても見た目が変わりません。色を使うサイトでは最初に6つとも定義しておきます。
タグ
コレクションの項目を "type": "tags" にすると、記事にタグを付けられます(1件20個まで・1タグ40文字まで・/ # ? は使えない)。タグは翻訳しません。
記事ごとのタグ — data-cms-each で1つずつ出します。中継がタグを {tag, url, color} にしているので、名前とタグページへのリンクが書けます。
<template data-cms-each="tags">
<a data-cms-attr="href:url" href="#">
<span class="sk-tag" data-cms-attr="data-color:color" data-cms="tag">タグ</span>
</a>
</template>タグの一覧(ナビ) — 使われているタグが <コレクション>_tags(例: news_tags)という一覧で届きます。{tag, count, url, color} を持ちます。
<template data-cms-list="news_tags">
<a data-cms-attr="href:url" href="#"><span class="sk-tag" data-cms="tag">タグ</span></a>
</template>タグページ — /news/tag/<タグ>/ は、中継が news/_tag.html を雛形にして描きます。news の一覧はそのタグで絞られた状態で届きます。表示名は news.tag です。存在しないタグでも 404 にはせず、空の一覧(data-cms-empty が出る)を返します。
<h1>タグ: <span data-cms="news.tag">タグ名</span></h1>
<ul>
<template data-cms-list="news">
<li><a data-cms-attr="href:url" href="./"><span data-cms="title">タイトル</span></a></li>
</template>
<template data-cms-empty="news"><li>このタグのお知らせはまだありません。</li></template>
</ul>タグの色は管理画面の「タグの管理」で決めます。色を決めていないタグには data-color 属性が付かず、.sk-tag の既定の見た目になります。
.sk-tag { display:inline-block; padding:.1rem .5rem; border-radius:999px;
font-size:.75rem; background:#f3f4f6; color:#374151; text-decoration:none; }
.sk-tag[data-color=red] { background:#fdecea; color:#c0392b; }
.sk-tag[data-color=gold] { background:#fdf6e3; color:#8a6d1f; }
.sk-tag[data-color=gray] { background:#f3f4f6; color:#4b5563; }
.sk-tag[data-color=black] { background:#1f2937; color:#fff; }
.sk-tag[data-color=green] { background:#e8f5ec; color:#15803d; }
.sk-tag[data-color=blue] { background:#e8effd; color:#1d4ed8; }data-cms-filter="tags=秋" のように書くと、タグページ以外でも絞り込めます(配列の項目は「含む」、1つの値は「等しい」で判定)。
種別のバッジ
select の選択肢には色を付けられます(schema.json)。公開 JSON には保存値に加えて <項目>_label(表示する文字)と <項目>_color(色名。色の無い選択肢では出ない)が載ります。
<span class="sk-badge" data-cms="category_label" data-cms-attr="data-color:category_color"></span>.sk-badge[data-color=…] の CSS は .sk-tag と同じ形で6色を書きます。
多言語
- schema.json の
localesに言語を並べます(例:["ja", "en"])。先頭が主言語で、お店の人はこの言語で入力します - 文章の項目(
text/textarea/blocks)は、保存のあと AI が自動で翻訳します(AI 翻訳のあるプランのとき)。訳さない項目には"translate": falseを付けます(時刻・日付・種別など) - 翻訳が無い・空の項目は主言語の値が出ます
- 言語ごとのページを用意し、中継の
CMS_LOCALESに言語:接頭辞を書きます(例:ja:/,en:/en)。言語は URL だけで決まり、Accept-Languageは見ません
data-embed を使う場合は、<html lang> か script の data-locale の言語で取得します。
AI 翻訳の有無と回数の上限はプランで決まります(プランと上限)。
書くときの決まり
守らなくてもエラーは出ず、表示が崩れるか、何も起きないだけです。うまくいかないときはここから確かめます。
- 印を付けた要素の中に、同じタグ名の要素を入れない。
<div data-cms="x"><div>…</div></div>は壊れます。<span>など別のタグを使います - 1つの要素に印は1つ。 2つ付けると後のものが勝ちます
data-cms-attrは1要素に1つだけ。href:url data-color:colorのように2つ書くと、どちらも書かれません。要素を分けます- タグ名と属性名は小文字で書く。
<DIV DATA-CMS="k">は無視されます <script>と<style>の中に印を書かない。 中身が書き換えられてしまいます(HTML コメントの中は安全です)<template data-cms-list>の中に、もう1つdata-cms-listを入れない。 1件の中で繰り返すときはdata-cms-eachを使いますdata-cms-filterで0件になってもdata-cms-emptyは出ません。data-cms-emptyが見るのは一覧そのものが空かどうかです- 静的な文言は、それだけ読んで意味が通る内容にする。 CMS に届かないときはそれが公開されます