制作サンプル/実案件の提案書として作成した仕様書の書式サンプルです。会社名・金額は創作です。

IMPLEMENTATION SPEC / WORDPRESS

物件サイトのWordPress実装仕様

静的HTMLで作ったデザインを、クライアント様ご自身で更新できる形にするための設計書です。テーマ構成の選択肢、データ設計、検索の実装、セキュリティ、納品前の確認項目までを1枚にまとめています。

WordPress 6.xPHP 8.1 以上 想定規模物件 300件 / 月間 1.5万PV 想定工数8〜24人日(構成による) 更新制作サンプル

1. 方針

物件情報は固定ページや投稿では管理しません。カスタム投稿タイプ+カスタムフィールドで構造化し、賃料・面積・駅徒歩を数値として持ちます。こうしておかないと、あとから「賃料が安い順」の並べ替えができません。

また、カスタム投稿タイプの登録はテーマの functions.php ではなくプラグインに書きます。テーマを入れ替えたときに、登録した投稿タイプごと消えて物件が全部見えなくなるためです。

この判断が効いてくる場面

2〜3年後にデザインをリニューアルするとき、データはそのままでテーマだけ差し替えられます。逆にテーマに書いてしまうと、リニューアルのたびにデータ移行が必要になります。

2. テーマ構成の3案

ご予算とご希望の動きの量によって、3つの選び方があります。

構成できること更新耐性工数費用の目安
A. 既存テーマそのまま
SWELL / Snow Monkey 等
テーマが用意している範囲のデザインと、フェードイン程度の動き 高 3〜5人日 45,000円〜
B. 子テーマ+独自テンプレート
トップとLPだけ独自
主要ページは自由に作り込み、下層は親テーマの資産を使う 中 9〜13人日 75,000〜105,000円
C. オリジナルテーマ 全ページを構造から設計。パララックス・SVGアニメーション・Canvasを自由に組める 高 18〜24人日 132,000円〜
Bの「更新耐性 中」について

親テーマのHTML構造が変わると、子テーマで上書きしたCSSがずれることがあります。親テーマのメジャーアップデート時には確認が必要です。この確認作業を保守契約に含めるかどうかを、契約時に決めておくことをお勧めします。

ご提案としてはBを推します。予算あたりの効果が最も高く、更新頻度の高いトップページに投資を集中できます。全ページに動きを入れたい場合はCになります。

3. 動きの実装可否

サンプルで作った動きを、それぞれの構成で実装できるかどうかの一覧です。

実装したい動きA 既存テーマB 子テーマ+独自C オリジナル備考
フェードイン・スライドイン可可可多くのテーマに標準機能あり
スクロール連動パララックス条件付可可Aは背景固定程度。多層は構造に手が要る
SVGの線画アニメーション不可可可SVGをテンプレートに直接書く必要がある
Canvasのアニメーション不可可可独自JSの読み込みが必須
横スクロールのタイムライン不可可可専用テンプレートが要る
絞り込み検索(本サンプルの機能)プラグイン可可Aは検索プラグイン依存。デザイン自由度は下がる
スクロールスパイ目次プラグイン可可—
ページビルダーでの実装を勧めない理由

Elementor等のアニメーション機能はフェードイン系に限られ、上表の下4行は実現できません。加えて、ビルダーが生成するDOMは階層が深く、読み込むCSS/JSが二重になって表示速度が落ちます。集客目的のサイトでは、速度低下が検索順位とCVRの両方に効きます。

動きを入れる場合の追加工数

項目内容工数
パララックス実装多層の背景、スクロール連動、reduced-motion対応1.5人日
SVGアニメーション図版の作図+線画アニメーション(1点あたり)1.0人日
Canvas描画データ連動の図(1点あたり)2.0人日
横スクロール実装スクロール量の計算、リサイズ対応、代替表示1.5人日
速度計測と調整Core Web Vitals の計測、遅延読み込み、再計測1.0人日

4. データ設計

ファイル構成

wp-content/plugins/estate-core/ ← データ定義はここ。テーマを替えても残る
 ├ estate-core.php  投稿タイプ・タクソノミーの登録
 ├ inc/meta-box.php  カスタムフィールドの保存
 └ inc/query.php   絞り込みクエリ
wp-content/themes/estate-child/ ← 見た目はここ
 ├ style.css
 ├ functions.php   CSS/JSの読み込みのみ
 ├ archive-property.php 物件一覧
 ├ single-property.php 物件詳細
 ├ page-top.php    トップ(動きを入れるページ)
 └ assets/js/motion.js パララックス等

カスタム投稿タイプ

estate-core.php
// プラグイン側で登録する。テーマの functions.php には書かない
add_action( 'init', function () {
    register_post_type( 'property', [
        'label'        => '物件',
        'public'       => true,
        'has_archive'  => true,
        'menu_icon'    => 'dashicons-admin-home',
        'supports'     => [ 'title', 'editor', 'thumbnail', 'revisions' ],
        'show_in_rest' => true,
        'rewrite'      => [ 'slug' => 'property', 'with_front' => false ],
    ] );

    register_taxonomy( 'area', 'property', [
        'label'        => 'エリア',
        'hierarchical' => true,
        'show_in_rest' => true,
    ] );
} );

カスタムフィールド

キー型用途備考
rent整数(円)賃料「12.5万円」ではなく 125000 で持つ
size_sqm小数専有面積並べ替えに使う
walk_min整数駅徒歩同上
layout文字列間取り1R / 1K / 1LDK … を選択式に
station文字列最寄駅—
features配列こだわり条件タクソノミーでも可
数値は必ず数値で保存する

「12.5万円」のような表示用の文字列で保存すると、並べ替えも範囲検索もできなくなります。表示は出力時に整形します。これは後から直すのが最も高くつく設計ミスです。

5. 絞り込み検索

URLのクエリ文字列で条件を受け取り、pre_get_posts でメインクエリを書き換えます。検索結果がURLで共有できる状態になります。

inc/query.php
add_action( 'pre_get_posts', function ( $q ) {
    if ( is_admin() || ! $q->is_main_query() || ! $q->is_post_type_archive( 'property' ) ) {
        return;
    }

    // 並べ替えは許可リスト方式。$_GET をそのまま orderby に渡さない
    $allowed = [ 'rent' => 'ASC', 'size_sqm' => 'DESC', 'walk_min' => 'ASC' ];
    $key     = sanitize_key( $_GET['sort'] ?? '' );

    if ( isset( $allowed[ $key ] ) ) {
        $q->set( 'meta_key', $key );
        $q->set( 'orderby', 'meta_value_num' );
        $q->set( 'order', $allowed[ $key ] );
    } else {
        $q->set( 'orderby', 'date' );
    }

    $meta = [ 'relation' => 'AND' ];

    if ( isset( $_GET['rent_max'] ) && $_GET['rent_max'] !== '' ) {
        $meta[] = [
            'key'     => 'rent',
            'value'   => absint( $_GET['rent_max'] ),
            'compare' => '<=',
            'type'    => 'NUMERIC',
        ];
    }

    if ( count( $meta ) > 1 ) {
        $q->set( 'meta_query', $meta );
    }
} );
やってはいけない書き方

$q->set( 'orderby', $_GET['sort'] ) のように、受け取った値をそのまま渡す実装です。orderby はSQLの構築に関わるため、許可リストで受ける形以外は書かないでください。

6. セキュリティ

保存処理の順序

カスタムフィールドの保存では、nonce検証 → 権限確認 → サニタイズ → 保存の順を必ず守ります。1つでも欠けると、第三者に管理画面のURLを踏ませるだけで値を書き換えられます。

inc/meta-box.php
add_action( 'save_post_property', function ( $post_id ) {
    // 1. 自動保存は対象外
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // 2. nonce の検証
    if ( ! isset( $_POST['estate_nonce'] )
        || ! wp_verify_nonce( sanitize_key( $_POST['estate_nonce'] ), 'estate_save' ) ) {
        return;
    }

    // 3. この投稿を編集する権限があるか
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    // 4. 型に応じてサニタイズしてから保存
    if ( isset( $_POST['rent'] ) ) {
        update_post_meta( $post_id, 'rent', absint( $_POST['rent'] ) );
    }
    if ( isset( $_POST['station'] ) ) {
        update_post_meta( $post_id, 'station',
            sanitize_text_field( wp_unslash( $_POST['station'] ) ) );
    }
} );

出力時のエスケープ

出力先の文脈に合わせて関数を選びます。「とりあえず esc_html」では属性値やURLで不足します。

出力先関数例
本文テキストesc_html()<p><?php echo esc_html( $name ); ?></p>
属性値esc_attr()title="<?php echo esc_attr( $t ); ?>"
URLesc_url()href="<?php echo esc_url( $u ); ?>"
HTMLを許す本文wp_kses_post()echo wp_kses_post( $body );
JSに埋める値wp_json_encode()var d = <?php echo wp_json_encode( $d ); ?>;

その他の必須項目

  • 全PHPファイルの先頭に if ( ! defined( 'ABSPATH' ) ) { exit; } を置き、直接アクセスを止める
  • 独自SQLは $wpdb->prepare() を通す。文字列連結でクエリを組まない
  • ログインURLの変更、管理画面のIP制限、ログイン試行回数の制限を設定する
  • 問い合わせフォームには reCAPTCHA ではなく、まず送信元と送信間隔の検証を入れる
  • WordPress本体・テーマ・プラグインの自動更新方針を、納品時に文書で取り決める
更新の運用について

脆弱性の大半はプラグインの放置が原因です。自動更新を有効にするか、月次の更新作業を保守契約に含めるかを、納品前に必ず決めてください。「あとで考える」が最も危険です。

7. 表示速度

動きを入れるほど、速度に注意が必要になります。以下は最低限の対応です。

集計結果のキャッシュ
function estate_area_counts() {
    $key  = 'estate_area_counts';
    $data = get_transient( $key );

    if ( false === $data ) {
        $data = /* 集計処理 */ [];
        set_transient( $key, $data, 6 * HOUR_IN_SECONDS );
    }
    return $data;
}

// 物件を保存したらキャッシュを捨てる
add_action( 'save_post_property', function () {
    delete_transient( 'estate_area_counts' );
} );
  • アニメーションは transform と opacity のみで組む。top / left / width は毎フレーム再レイアウトが走る
  • スクロール処理は requestAnimationFrame でまとめる。scroll イベントで直接DOMを触らない
  • 画面外の要素は IntersectionObserver で止める
  • 画像は loading="lazy" + WebP。ただしファーストビューの画像には付けない(LCPが遅くなる)
  • prefers-reduced-motion に対応する。アクセシビリティ要件であり、実装コストはほぼゼロ

8. 納品前チェック

チェックすると進捗が動きます。担当欄は、その項目を誰が確認するかです。

0 / 18
制作
制作
制作
制作
制作
制作
制作
共同
共同
共同
共同
共同
制作
共同
制作
客先
客先
客先

9. お問い合わせ

この仕様書の内容についてのご質問、既存サイトの改修のご相談も承ります。他社が作ったWordPressサイトの引き継ぎもお受けしています。管理画面に入れる状態であれば、現状の構成を調査してご報告します。

10. このページで使っている実装

この仕様書そのものも、読ませるための作りにしています。

実装目的
追従目次(スクロールスパイ)
IntersectionObserver
いま読んでいる節を左の目次で示します。長い文書は「どこを読んでいるか」が分からなくなると読み止められます。
読み進みバー画面上端の線が全体の進捗を示します。残量が見えるだけで読了率が変わります。
コードのコピー
Clipboard API
コード枠のボタンで全文をコピーできます。API が使えない環境では、その旨をボタン上に表示して黙って失敗させません。
チェックリストの集計18項目のチェック状況を数と帯で集計します。納品前の確認を、そのままこの画面で進められます。
ファイル構成の段階表示画面に入ると1行ずつ現れます。階層の理解を助けるための順序で並べています。
↑TOP