何を解決するために作ったのか
LPは 「速く作って、速く公開して、効果がなければ捨てる」 ものです。 ところが既存のCMSは、1サイトを長く運用する前提で作られています。 LPを10枚作ると、データベースが10個増えます。
必要だったのは、次の3つを同時に満たすものでした。
- 設置が軽いこと — データベースの作成も、移設時のダンプも要らない
- 1枚ごとに違ってよいこと — LPは宛先も電話番号もページごとに違う
- LPページが壊れないこと — 編集する人がHTMLを知らなくても、レイアウトが崩れない
既製品を組み合わせるより、作ったほうが速いと判断しました。 外部ライブラリは1つも使っていません(composer も npm も使いません)。 依存が増えるほど、納品後に「動かない」が増えるためです。
LPツクレールとは
このツールで作った3枚です。骨格から作り分けています。 色を変えただけの使い回しではありません。枠の中はスクロールできます。



テンプレートは「複製して増やす」ための機能で、04 で扱います。 ここで見せたいのは、同じツールでまったく違う型を作れることです。
LPツクレールの管理画面
ブロックを上から積むだけです。HTMLもCSSも書きません。 1枚の画面に、ページの中身・ヘッダー・構造化データ・公開設定がすべて収まっています。
ブロックそのものも折り畳めます。畳むと 「入力項目4件(お名前 / メールアドレス / …)」のような要約が出るので、 開かなくても中身が分かります。並べ替えと削除は畳んだままでも使えるため、 長いページの構成を組み替えるのが速くなります。「すべて閉じる/すべて開く」も付けました。
編集者が壊せる余地を、意図的に狭くしています。 余白は「なし・小・中・大」から選ぶだけで、数値は入力させません。負の値を1つ入れられるだけで レイアウトは破綻します。色も5色のプリセットからで、カラーピッカーは付けていません(装飾の一部は、許可した範囲でCSSを直接指定できます)。
最初はサイト共通にしていました。実際に使ってみると、LPは1枚ごとに宛先も電話番号も違います。 「サイト」ではなく「1枚」が単位でした。ここで設計を入れ替えています。
量産の入口
新規作成すると、まずテンプレートを選びます。白紙を含めて11種類(白紙・シンプルLP・ サービス紹介・商品販売・セミナー/イベント告知・資料請求・採用・お問い合わせ・会社概要・ キャンペーン・よくある質問)。 既存のページをそのままテンプレートとして登録することもできます。
/ が404になり、しかも気づきにくいためです。理由を画面に書いています。
装飾は迷わせない、最低限を実装
装飾の選択肢は、意図的に絞っています。選べる項目が多いほど、作る人は迷います。 そのうえで、実際にLPを組んで「これが無いと成立しない」と分かった2つだけを足しました。 飾りを増やすためではなく、枚数を作ることに集中できるようにするためです。
画像の上に文字を重ねる
写真編集ソフトで文字を焼き込む方法は採りませんでした。 焼き込むと、検索エンジンにも読み上げにも社名が伝わらず、スマートフォンで縮んだときに読めません。 ここで入れた文字は本物のテキストとして出力されます。
位置と明暗を両方持たせたのは、3枚とも条件が違ったからです。 ジムは左が明るく、喫茶店は右の壁が暗く、歯科は中央に余白がありました。 固定値にすると3枚のうち2枚が読めなくなります。読みやすさのため、文字の側にだけ薄い幕をかけています。
スクロールでふわっと出す
この種の演出は、アニメーションが動かなかったときに要素が見えるか消えるかを先に決めておく必要があります。
要素を隠す処理は、JavaScriptではなくCSSに持たせました。
CSSは @media (scripting: enabled) の中で要素を隠します。
JavaScriptが動かない環境ではこの条件が偽になるため、ブラウザは隠す指定を適用せず、要素をそのまま表示します。
CSSはページの読み込み時点で効くので、要素が一度見えてから消えるちらつきも起きません。
※ OSで「動きを減らす」を選んでいる利用者には、ブラウザがアニメーションを実行しません。要素は最初から表示されたままになります。
なぜデータベースを使わないのか
| 設置 | ファイルをアップロードするだけ。データベースの作成も接続情報の設定も要りません |
|---|---|
| 移設 | フォルダごとコピーすれば動きます。ダンプもインポートも不要 |
| バックアップ | フォルダを丸ごと取れば完了。取り忘れる部分がありません |
| 費用 | データベースの無いプランでも動きます |
代わりに、データベースが面倒を見てくれていた部分を自分で作る必要がありました。
- 書き込みの競合 —
flock()で排他し、一時ファイルへ書いてから差し替える(書きかけのファイルが残らない) - 同時編集 — ページごとに版番号を持ち、古い版での上書きを拒否する。3人で同時に編集する検査を入れています
- 履歴 — 保存のたびに世代バックアップを取る
public_html の中に入るため、
入口が自分の位置を辿って app/ を探す作りにしています。
HTMLを保存しない
本文を「自由なHTML」として持っていません。種類と値だけの構造化データとして保存し、 表示するときに組み立てます。
{ "type": "heading", "level": 2, "text": "料金", "deco": "band", "color": "green" }
{ "type": "image", "src": "hero.jpg", "over": "STUDIO ARC", "over_pos": "left" }
保存されるのは値だけです。編集者が <script> と入力しても、
そう表示されるだけでスクリプトにはなりません。
HTMLサニタイザは、書き方の抜け道が見つかるたびに更新が要ります。外部ライブラリへの依存も増えます。 値しか保存しない設計なら、抜け道を探す相手がいません。
見た目の調整は3つに限定しました。表の列ごとの配置のような意味のある属性、 設定ファイルで許可したクラス名だけを選ぶバリエーション、 比率がプリセットで入れ子が1段までのカラム。自由なCSSは受け取りません。
色と内側の余白だけは、ブロックごとに直接指定できます。ここも
position・margin・@media・url()・
!important・負の値は拒否します。13項目の検査で確かめています。
CSPは script-src 'self'。unsafe-inline は開けません。
構造化データやプレビューのインラインCSSは、内容のSHA-256をCSPに載せて通しています。
ログインと権限
ユーザーの追加はメール招待です。保存するのはトークンのハッシュだけで、 招待リンクを再発行できる状態にはしていません。
認証アプリに読ませるQRコードを、外部ライブラリなしで作っています。
リード・ソロモン符号・マスク8種・型番の自動選択まで実装し、
規格の値および参照実装との一致を36項目の検査で確かめています。
出力するSVGにはインラインの style も外部参照も入れません(CSPを緩めないため)。
ファイルへの書き込みは flock() で排他し、一時ファイルへ書いてから差し替えます。
途中で処理が止まっても、書きかけのファイルが残りません。
さらにページごとに版番号を持ち、古い版での上書きは拒否します。 あとから保存した人には「他の人が更新しています」と伝わるので、 気づかないうちに消えることがありません。 3人が同時に編集する場面を検査に入れています。
お問い合わせ
管理者へ通知し、送信者へ控えを返します。迷惑送信への対策は、 送信の挙動と本文の両面を、複数の条件で見て判定しています。 判定の一部は、設置先の事情に合わせて管理画面から調整できます。
例外は1項目だけ——正当な送信者が自分で気づいて直せるものに限り、理由を伝えます。 誤って弾いた人を、直しようのないまま帰さないためです。 記録には生のIPを残さず、ハッシュ化して保存します。
変更できると、大きな問題に発展しかねません。 ここは設定ファイルを直接編集する形にし、その手順を取扱説明書に書いています。
特商法・規約のページ
LPで物やサービスを売るなら、特定商取引法に基づく表記・ 利用規約・プライバシーポリシーが要ります。 ところが「何をどう書けばいいか」で止まりがちです。 入力欄そのものを書式にして、白紙から書かせないようにしました。
| 特定商取引法に基づく表記 | 販売業者・所在地・支払方法・引渡時期・返品の扱いなど、 記載が必要な11項目があらかじめ並びます。内容を埋めるだけです |
|---|---|
| 利用規約 | 適用・禁止事項・提供の停止・免責・準拠法など7条の下書き |
| プライバシーポリシー | 取得する情報・利用目的・第三者提供・開示請求など8条の下書き |
規約は別ページとして作らせません。LPに入力すると、
/<LPのURL>/terms/ のような専用URLが自動でできて、
フッターのリンクから新しいタブでその規約だけが開きます。
LPを複製すれば規約もついてきますし、LPを消せば規約も一緒に消えます。
ページ一覧が規約で埋まることもありません。
特定商取引法のページには、所在地の地図も出せます。
住所は会社情報から取るので、入力し直す必要はありません。
地図を出すページだけ、外部の枠内表示を許可する指定
(frame-src)を足します。出さないページでは閉じたままで、
スクリプトの許可には一切触れていません。
入力されるひな形は書式の見本であって、そのまま使える完成品ではありません。 記載内容が要件を満たしているかは、依頼元またはお近くの専門家の確認が要ります。 管理画面にもそう明記しています。
納品後について
FTPを触れない人でも設定を変えられるようにしています。
問い合わせは保存日数を過ぎると自動で消えます(サーバーの定期実行)。
取扱説明書はHTML1枚で同梱し、更新時に data を上書きしてはいけないことを
赤枠で書いています。ここを間違えると、ページも問い合わせも消えます。
自動テスト 631項目
画面を目で見て確かめるのは、回数が増えると必ず抜けます。 コマンド1つで631項目を通すようにしました。実際に本番へ上げる前に、毎回これを通します。
| 壊れやすい所 | 同時編集の競合、URLの重複、予約公開、ゴミ箱、トップページの入れ替え |
|---|---|
| 安全側 | XSS、CSRF、権限、セッション、CSPのハッシュ、招待トークン、通知先が漏れていないこと |
| 規格 | TOTP(RFC 6238のテストベクタ)、QRコード(生成多項式・シンドローム・参照実装との一致) |
| 整合 | SNSの判定表がPHPとJavaScriptで一致すること、編集画面の入力欄と保存名が食い違わないこと |
見出しの段数が抜けたときに <h0> という無効なタグが出ること、
表の見出しの空セルが捨てられて列が1つずれること。
どちらも通常の操作では起きませんが、直したうえで検査を足しました。
構成と規模
| PHP | 49ファイル / 約11,442行 |
|---|---|
| CSS・JavaScript | 約2,644行 |
| クラス | 20(Auth Blocks Csrf Css Form Qr Totp Webauthn ほか) |
| 外部ライブラリ | なし(composer / npm を使っていません) |
| 動作環境 | PHP 8.3以上 / エックスサーバー |
| 状態 | 本番稼働中。取扱説明書つきで配布できる状態 |