ポートフォリオに戻る

🔒 守秘義務案件のため、クライアント名・URL・スクリーンショット等の詳細情報を省略しています。

理容サロンチェーン公式サイト(東京都内・複数店舗展開)

Wordpress

こちらの案件は、下記に記載の制作会社様からの受託案件の中の一部をピックアップしたものです。


アーキテクチャ面

WordPress カスタムテーマ + 静的 LP のハイブリッド構成

メインサイトは WordPress カスタムテーマ salon_name として完全スクラッチ開発し、予約・採用など訴求軸が異なるコンテンツは /open ディレクトリの静的 HTML5 LP として切り出した。LP 側も同一リポジトリで管理し、SCSS ビルドパイプラインを共有することで変数・mixin の一貫性を保っている。

レイヤー役割主な内容
WordPress テーマ salon-name/コンテンツ管理・全ページ出力カスタム投稿タイプ・SCF フィールド定義・全テンプレート・SCSS/JS
静的 LP open/プロモーション訴求独立 HTML + SCSS(テーマ変数を import)・Slick スライダー
カスタムプラグイン editor-extention/管理画面 UX 改善不要メタボックス削除・文字数カウンター表示

カスタム投稿タイプ設計(4 CPT + 複数タクソノミー)

サロンビジネスで必要なコンテンツをすべて投稿タイプ単位で独立モデル化し、ブログ(post)はタグ・担当者の関連付けのみ追加した。タクソノミーはアーカイブ URL とフィルター UI を兼ねる設計にし、ページネーションや絞り込みを WP 標準機能で賄えるようにした。

投稿タイプスラッグタクソノミー主な SCF フィールド
ヘアスタイルhairstylehairstyle_length / hairstyle_color / hairstyle_style / hairstyle_tag繰り返し画像・YouTube/動画 URL・担当スタッフリレーション
スタッフstaffstaff_position氏名(日本語・ローマ字)・役職・担当メニュー・Instagram 埋め込み・予約 URL
メニューmenu詳細グループ(繰り返し)・メニュー名・価格・注意書き
クーポンcouponcoupon_category対象サービス(複数チェックボックス)・通常/クーポン価格・関連ブログ投稿
店舗情報オプションページロゴ・TEL・営業時間・Googleマップ URL・GA タグ・Googleカレンダー埋め込み・クレジットカード可否(5ブランド)・イベントタグ(繰り返し)

SCF オプションページによる店舗グローバル設定

店舗共通の情報(TEL・住所・カレンダー・GA タグ等)を SCF オプションページ 1 か所に集約し、SCF::get_option() でどのテンプレートからも参照できる設計にした。将来の多店舗化にも、オプションページを複製するだけで対応できる拡張性を持つ。

// cpt-shopinfo.php — SCF オプションページ登録
$setting = SCF::add_setting( 'shopinfo-setting', '店舗情報' );
$setting->set_title_target( 'options' );
$setting->add_group( 'credit', false, [
    [ 'name' => 'visa',       'label' => 'VISA',       'type' => 'checkbox' ],
    [ 'name' => 'mastercard', 'label' => 'Mastercard', 'type' => 'checkbox' ],
    // ...
] );

// テンプレート側での利用
$tel = SCF::get_option( 'shop_tel' );

WordPress カスタム開発

functions.php 主要実装

1. 不要な wp_head 出力の除去

ページ速度改善と情報漏えい防止のため、デフォルトで出力される不要なメタ情報を一括削除した。Gutenberg ブロックライブラリの CSS も非テーマページでは除外し、リクエスト数を最小化した。

remove_action( 'wp_head', 'wp_generator' );
remove_action( 'wp_head', 'feed_links', 2 );
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
add_action( 'wp_enqueue_scripts', function() {
    wp_dequeue_style( 'wp-block-library' );
} );

2. スクリプト条件分岐エンキュー

Slick カルーセルをヘアスタイル詳細・スタッフ詳細・トップページの 3 条件でのみ読み込み、不要ページへの JS ロードを排除した。採用ページ専用スタイルシートも同様に条件付きエンキューで分離した。

function salon_scripts() {
    wp_enqueue_script( 'jquery', 'https://code.jquery.com/jquery-3.6.0.min.js', [], null, true );
    wp_enqueue_style(  'ress',   get_template_directory_uri() . '/node_modules/ress/dist/ress.min.css' );

    if ( is_front_page() ) {
        wp_enqueue_script( 'topslide', get_template_directory_uri() . '/js/topslide.js', ['jquery'], null, true );
        wp_enqueue_script( 'slick',    '//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js', ['jquery'], null, true );
    }
    if ( is_singular('hairstyle') || is_singular('staff') ) {
        wp_enqueue_script( 'styleslide', get_template_directory_uri() . '/js/styleslide.js', ['jquery'], null, true );
        wp_enqueue_script( 'slick',      '//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js', ['jquery'], null, true );
    }
    if ( is_page('recruit') || is_page('recruit_new') ) {
        wp_enqueue_style( 'recruit', get_template_directory_uri() . '/recruit.css' );
    }
}

3. 動的メタデータ / OGP 生成

my_get_metadata() が投稿タイプ・タクソノミー・ページスラッグを判定し、SCF オプションページおよび各投稿の SCF フィールドから title / description / keywords を動的生成する。header.php はこの関数の戻り値を展開するだけで、SEO 設定の重複ロジックが一切発生しない。

// template-functions.php
function my_get_metadata() {
    if ( is_front_page() ) {
        return [
            'title'       => SCF::get_option('top_title'),
            'description' => SCF::get_option('top_description'),
            'keywords'    => SCF::get_option('top_keywords'),
            'h1'          => SCF::get_option('top_h1'),
        ];
    }
    if ( is_singular('hairstyle') ) {
        return [
            'title'       => get_the_title() . ' | スタイル | ' . SCF::get_option('shop_name'),
            'description' => SCF::get('hairstyle_description'),
            // ...
        ];
    }
    // 他の投稿タイプ・タクソノミー・ページも同様に分岐
}

4. ページ別 body ID の動的付与

投稿タイプやページスラッグに応じて <body id="..."> を動的に設定し、CSS セレクターを ID で一意化することでスタイルシートの詳細度管理を単純化した。

function my_body_id() {
    $id = 'other';
    if ( is_front_page() )        $id = 'top';
    if ( is_singular('staff') || is_post_type_archive('staff') )       $id = 'staff';
    if ( is_singular('hairstyle') || is_tax('hairstyle_length') )      $id = 'style';
    if ( is_singular('menu') || is_post_type_archive('menu') )         $id = 'menu';
    if ( is_singular('coupon') || is_post_type_archive('coupon') )     $id = 'coupon';
    if ( is_page('ranking') )     $id = 'ranking';
    if ( is_page('reserve') )     $id = 'reserve';
    if ( is_page('recruit') || is_page('recruit_new') ) $id = 'recruit';
    if ( is_singular('post') || is_home() ) $id = 'blog';
    return $id;
}

テンプレート構成(20 ファイル超)

WP テンプレート階層をフルに活用し、投稿タイプ・タクソノミー・ページスラッグ単位でテンプレートを分割した。各テンプレートは get_template_part() で共通パーツ(ヘッダー・フッター・サブヘッダー)を再利用する。

テンプレート役割
front-page.phpトップページ(MV スライダー・スタイルカルーセル・新着スタッフ・クーポン)
single-hairstyle.phpスタイル詳細(Slick ギャラリー・YouTube/動画埋め込み・タグ一覧)
archive-hairstyle.phpスタイル一覧(タクソノミーフィルター連動)
taxonomy-hairstyle_length.php長さカテゴリー絞り込みページ
taxonomy-hairstyle_style.phpスタイルカテゴリー絞り込みページ
single-staff.phpスタイリスト詳細(Slick ギャラリー・Instagram 埋め込み・予約ボタン)
archive-staff.phpスタッフ一覧(EO Calendar 連携・イベント表示)
archive-menu.phpメニュー一覧(繰り返しグループ展開)
archive-coupon.phpクーポン一覧(カテゴリータクソノミー別)
page-ranking.phpランキングページ(SCF ラジオ値でクエリソート)
page-recruit.php採用ページ(専用 recruit.css を条件エンキュー)
page-recruit_new.php採用ページ刷新版(デザイン差し替え済み)

管理画面 UX カスタマイズ(カスタムプラグイン)

編集者向けに不要なメタボックスを非表示にし、ブログ投稿タイトルへの文字数インジケーターを追加した。All in One SEO Pack のメタボックスも除去し、テーマ側 SCF オプションページに SEO 設定を一元化した。

// editor-extention.php
function remove_default_post_screen_metaboxes() {
    foreach ( ['custom-fields','excerpt','comments','trackbacks',
               'author','slug','revisions','aiosp_snippet'] as $id ) {
        remove_meta_box( $id, get_current_screen(), 'normal' );
    }
}
// タイトル文字数カウンター(黄色インジケーター)
add_action( 'admin_head', function() {
    echo '';
} );

SCSS ビルド設計

変数・mixin による基盤共有

SCSS を inc/_base.scss(変数・mixin)/ style.scss(メインシート 2,500 行超)/ recruit.scss(採用ページ専用)の 3 ファイルに分割し、静的 LP の open/css/_base.scss も同一変数体系を共有した。node-sass --watch によるビルドスクリプトを package.json に定義し、保存時の自動コンパイルを実現した。

主要変数(_base.scss):

// フォントスタック
$base-font:   "游明朝体", YuMincho, "Hiragino Mincho Pro", serif
$gothic-font: "游ゴシック", YuGothic, "Hiragino Kaku Gothic Pro", sans-serif
$cinzel-font: 'Cinzel', serif  // Google Fonts

// ブレークポイント
$bp:     768px   // タブレット
$bp-pc: 1200px   // PC

// 基準表示幅(vw 計算用)
$body-width-sp: 750px
$body-width-pc: 1200px
$inner-width:  1100px   // コンテンツ最大幅

// カラー
$main-color: #000000
$crimson:    #d90000  // アクセント
$gold:       #bea032  // ランキング 1 位
$silver:     #a0a0a0  // ランキング 2 位
$bronze:     #be5332  // ランキング 3 位

主要 mixin(_base.scss):

// vw 相対フォントサイズ(px 指定を vw に自動換算)
@mixin calc-fontsize($size, $displaysize: $body-width-sp) {
    font-size: $size / $displaysize * 100vw;
}

// レスポンシブブレークポイント
@mixin smp { @media (max-width: #{$bp - 1px})  { @content; } }
@mixin tab  { @media (min-width: $bp)           { @content; } }
@mixin pc   { @media (min-width: $bp-pc)        { @content; } }

// object-fit ユーティリティ
@mixin object-fit($position: center center) {
    object-fit: cover;
    object-position: $position;
    font-family: 'object-fit: cover; object-position: #{$position}';  // polyfill
}

// クロスブラウザ Flexbox
@mixin flexbox($wrap: wrap, $justify: flex-start, $align: stretch) {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: $wrap;
    flex-wrap: $wrap;
    justify-content: $justify;
    align-items: $align;
}

レスポンシブ設計(SP ファースト・2 ブレークポイント)

モバイルファーストで記述し、タブレット(768px)・PC(1200px)は mixin で追記する構造を統一した。フォントサイズと余白は calc-fontsize / calc-size 関数で vw 換算し、固定 px 値の羅列を排除している。アスペクト比保持には padding-bottom パーセント技法を採用し、画像差し替えに対してレイアウトが崩れない設計にした。

デバイス切り替え方法
スマートフォン(基準)〜767pxデフォルトスタイル(calc-fontsize による vw 指定)
タブレット768px〜@include tab { ... }
PC1200px〜@include pc { ... }

採用ページ専用スタイルシートの分離

採用ページは訴求対象が異なりデザイン刷新が頻繁なため、recruit.scss を独立コンパイルし、メインシートとは切り離した。functions.php の条件付きエンキューと組み合わせることで、採用ページのビジュアル変更がサイト全体に影響を与えない構造にした。


JavaScript インタラクション

jQuery ベースで 3 ファイルに役割分離し、ライブラリは Slick Carousel のみに限定することで依存を最小化した。

ファイル実装内容
common.jsハンバーガーメニュー開閉アニメーション・ヘアスタイル一覧のサムネイル切り替え・ブログサイドバーの「人気 / アーカイブ」タブ切り替え
topslide.jsトップページ MV スライダー(Slick・無限ループ・5秒自動再生・500ms フェード)
styleslide.jsヘアスタイル詳細ギャラリー(Slick・ドットナビ・矢印非表示・自動再生なし)
open/js/index.js静的 LP のヒーロースライダー(Slick・同一設定を独立管理)
// topslide.js — トップページスライダー初期化
$('#mv-slider').slick({
    infinite:      true,
    autoplay:      true,
    autoplaySpeed: 5000,
    speed:         500,
    arrows:        false,
    dots:          true,
});

// common.js — サムネイル切り替え(ヘアスタイル一覧)
$('.style-thumb').on('click', function() {
    var src = $(this).attr('src');
    $(this).closest('.style-item').find('.style-main-img').attr('src', src);
});

まとめ

項目内容
種別理容サロンチェーン公式サイト(複数店舗展開)
技術スタックWordPress + PHP / カスタムテーマ(フルスクラッチ開発)+ 静的 HTML5 LP
投稿タイプ設計カスタム投稿タイプ 4 種・タクソノミー 7 種・SCF フィールド 30+ グループ
SCF 活用オプションページで店舗情報を一元管理・繰り返しグループで動的データ構造
テンプレートWP テンプレート階層をフル活用(20 ファイル超)・動的メタデータ / OGP 生成
管理画面 UXカスタムプラグインで不要メタボックス削除・タイトル文字数カウンター追加
SCSSvw 相対計算 mixin・SP ファースト 2 ブレークポイント・採用ページ専用シート分離・LP との変数共有
フロントエンドjQuery + Slick カルーセル・条件付きエンキューによる不要 JS ロード排除
SEO / 計測投稿タイプ別動的 OGP・GA タグ・SCF イベントタグ(繰り返し)・wp_head 出力整理