Reactを基礎から学び直す #1: なぜ今、学び直すことにしたのか

フリーランスでWeb制作をしている矢野です。

普段の私は、本業ではチャット型LPのReact実装やCVR改善を担当し、副業ではWordPressでのコーポレートサイト制作を中心にしています。

Reactは仕事で毎日触っているので、動くコードは書けます。

ただ正直に言うと、「なぜこの設計にしたのか」を基礎から筋道立てて人に説明できるかというと、あまり自信がありませんでした。

普段の私とReactの距離感

本業ではすでに動いているプロダクトの改修が中心なので、既存のコンポーネント構成やHooksの使い方に沿って実装することがほとんどです。

一方で副業のWeb制作はWordPressでのコーポレートサイト制作が中心なので、Reactを「ゼロから設計する」機会自体があまりありませんでした。

その結果、Reactを使えてはいるものの、Props・State・Hooksあたりの基礎を体系立てて説明する自信は正直なかったのです。

きっかけになった、ポートフォリオ4作品目

そんな状態を自覚したのが、ポートフォリオの4作品目として、React・Next.js製の診断アプリ「RE:FIT」を作ったときでした。

パーソナルトレーニングジムを想定し、目的・運動経験・通える頻度を答えると、その場でおすすめプランを診断して体験予約まで進める、というミニアプリです。

診断の質問から結果、予約までの状態を管理する部分で、useStateを並べるだけでは分岐が追いきれなくなり、useReducerで状態遷移を1箇所にまとめる設計にしました。

type DiagnosisAction =
  | { type: "SET_GOAL"; goal: Goal }
  | { type: "NEXT" }
  | { type: "BACK" }
  | { type: "RESTART" };

書きながら「この設計にした理由」は自分の中では明確だったのですが、いざ言語化しようとすると、なぜuseStateの複数管理ではなくuseReducerを選んだのかを、基礎から順序立てて説明できる言葉がすぐには出てきませんでした。

動かせることと、人に説明できることは別だと痛感した瞬間です。

「なんとなく動く」と「人に説明できる」の差

この差は、本業でも副業でも地味に効いてきます。

本業でチームに設計意図を伝えるときも、副業でクライアントに技術的な判断根拠を説明するときも、求められているのは「動くコード」そのものより「なぜそう作ったかを言語化する力」だからです。

裏を返せば、基礎から体系的に学び直す過程を発信すること自体が、初心者にも分かりやすく説明できる実務力の証明になるはずだと考えました。

このシリーズでやること

そこで、Reactを基礎から学び直す過程を、そのまま記事シリーズとして公開していくことにしました。

全7回を予定していて、コンポーネント設計の基礎、Hooksの実践的な使い方、状態管理の整理、Next.jsとの組み合わせ方、TypeScriptでの型付けと進めていきます。

最終回では、学び直した基礎を踏まえて、今回作った「RE:FIT」の実装をあらためて振り返る予定です。

自分自身の理解を体系立て直すログが、同時にポートフォリオの技術力を伝えるコンテンツにもなる、という一石二鳥を狙っています。

まとめ

今回は、Reactを基礎から学び直すことにした理由と、このシリーズの狙いを書きました。

きっかけは、実際に作った診断アプリの状態管理を人に説明しようとして、うまく言葉にできなかった経験です。

次回は、シリーズの土台となるコンポーネント設計の基礎(関数コンポーネント・Props・State)から始めます。

WordPress Playgroundを実際に触ってみた話(wp-env比較つき)

フリーランスでWeb制作をしている矢野です。

前回の記事で、wp-env(Docker)を使ったWordPressのローカル開発環境構 築について書きました。

今回はその続編として、ブラウザだけでWordPressを動かせるという「WordP ress Playground」を実際に触ってみた話です。

Playgroundとは何か

WordPress Playgroundは、PHPをWebAssembly(WASM)にコンパイルし、ブラ ウザの中だけでWordPressを丸ごと動かせるツールです。

サーバーはもちろん、Dockerも必要ありません。

wp-envのようにDocker Desktopの起動を待つ必要がなく、コマンド一発でWordPressの管理画面が触れ る、という触れ込みに惹かれて検証してみました。

npx一発で動いた話

まずはCLIツールの@wp-playground/cliを試しました。

前提条件はNode.js 20.18以上だけで、個別インストールも不要です。

npx
  @wp-playground/cli@latest server --port=9401

このコマンド1つで、WordPressのコアをダウンロードしてきて、PHP 8.3・最新版のWordPressがその場で立ち上がります。

wp-envのように.wp-env.jsonを書いたり、Docker Desktopを起動して待ったりする手間は一切ありませんでした。

実測:初回130秒→2回目29秒

体感の軽さを数字で確認したかったので、起動時間を実際に計測しました。

初回起動(WordPress本体のダウンロードを含む)は約130秒でした。

一方、2回目以降の起動はダウンロード済みのファイルが使い回されるため 、約29秒まで短縮されます。

Docker Desktopの起動待ちそのものが発生しない分、体感としてもこの数字 以上に軽く感じました。

普段使っているwp-envは今回の検証時も別案件(おうちで、わんこと。)用に 稼働中だったので、作業中の環境を壊したくなく、公平な比較のための再計測 はあえて見送っています。

自作テーマをマウントして動かしてみた

ただ起動できるだけでは検証として不十分なので、実際に自作テーマをマウ ントして動かせるか試しました。

--mountオプションで、ホスト側のディレクトリをWordPress 内の任意のパスに割り当てられます。

npx
  @wp-playground/cli@latest server --port=9403 \
    --mount=/Users/yanoyuuki/Documents/jj-company/wanko-to-theme:/wordpre
  ss/wp-content/themes/wanko-to-theme \
    --login

このコマンドで、ポートフォリオ作品「おうちで、わんこと。」のテーマデ ィレクトリをそのままマウントしてみたところ、テーマファイルは200で配信さ れ、管理画面のテーマ一覧にも「wanko-to-theme」として正しく認識されまし た。

wp-envと同じ感覚で、手元のテーマディレクトリをそのまま検証環境に持ち 込めると分かりました。

ハマったポイント①:–loginオプションの302リ ダイレクト

--loginオプションを付けると自動ログインしてくれるのです が、初回アクセス時にステータスコード302が返ってきて、少し戸惑いました。

curl -I
  http://127.0.0.1:9403/
  # HTTP/1.1 302 Found
  # location: /

調べてみると、自動ログインの処理自体がCookieをセットしたうえで同じUR Lへリダイレクトする仕組みになっているだけで、エラーではありませんでした 。

ブラウザで開く分には自動的にリダイレクトされるので気にする必要はない のですが、curlなどでヘルスチェック的に叩く場合は302も正常系として扱う必 要がある、という点は覚えておきたいポイントです。

ハマったポイント②:–skip-browserはstartコ マンド専用

もう1つ、serverコマンドにブラウザを自動起動させない–skip-browserオプションを渡したところ、エラーになりました。  

BEFORE(うまくいかなかったコマンド)

npx
  @wp-playground/cli@latest server --skip-browser --port=9401
  # Unknown arguments: skip-browser, skipBrowser

AFTER

--skip-browserは、プロジェクトの種類を自動判定してくれ る簡易版のstartコマンド専用のオプションで、低レベルなserverコマンドには存在しないオプションでした。

serverコマンドはもともとブラウザを自動起動しない仕様な ので、このオプション自体が不要だったというオチです。

startserverでオプション体系が違う点は、 公式ドキュメントを読むだけでは気づきにくく、実際に手を動かしてよかった ポイントでした。

wp-envとの使い分け

ここまで検証してみて、Playgroundとwp-envは競合するというより役割が違 うツールだと感じました。

Playgroundは、Docker Desktopの起動待ちがなく、npx一発でPHP・WordPressのバージョ ンを切り替えながらサクッと検証できる手軽さが強みです。

ブラウザで開く分には自動的にリダイレクトされるので気にする必要はない のですが、curlなどでヘルスチェック的に叩く場合は302も正常系として扱う必 要がある、という点は覚えておきたいポイントです。

ハマったポイント②:–skip-browserはstartコ マンド専用

もう1つ、serverコマンドにブラウザを自動起動させない–skip-browserオプションを渡したところ、エラーになりました。

BEFORE(うまくいかなかったコマンド)

npx
  @wp-playground/cli@latest server --skip-browser --port=9401
  # Unknown arguments: skip-browser, skipBrowser

AFTER

--skip-browserは、プロジェクトの種類を自動判定してくれ る簡易版のstartコマンド専用のオプションで、低レベルなserverコマンドには存在しないオプションでした。

serverコマンドはもともとブラウザを自動起動しない仕様な ので、このオプション自体が不要だったというオチです。

startserverでオプション体系が違う点は、 公式ドキュメントを読むだけでは気づきにくく、実際に手を動かしてよかった ポイントでした。

wp-envとの使い分け

ここまで検証してみて、Playgroundとwp-envは競合するというより役割が違 うツールだと感じました。

Playgroundは、Docker Desktopの起動待ちがなく、npx一発でPHP・WordPressのバージョ ンを切り替えながらサクッと検証できる手軽さが強みです。

一方でwp-envは、MySQLを使った本番相当の構成で、腰を据えてテーマ・プ ラグインを開発するのに向いています。

「ちょっとこの挙動だけ確認したい」ときはPlaygroundを、「本格的にテー マを作り込む」ときはwp-envを、というのが今のところの使い分けの結論です 。

まとめ

今回は、WordPress Playgroundを実際にCLIで動かし、起動時間の実測や自作テーマのマウント、ハ マったポイントまで含めて検証した話を書きました。

Docker不要で手軽に試せる分、日々のちょっとした検証や動作確認にはかな り相性が良いツールだと感じています。

wp-envと使い分けながら、これからも開発環境の選択肢として活用していく つもりです。

プラグインなしでWordPressのお問い合わせフォームを自作した話

フリーランスでWeb制作をしている矢野です。

今回は、ポートフォリオ作品「おうちで、わんこと。」(訪問型ペットシッターサービスの架空クライアントサイト)で、Contact Form 7のようなプラグインを使わずに自作したお問い合わせフォームについて書きます。

なぜプラグインを使わずに自作したか

WordPressのお問い合わせフォームといえば、Contact Form 7やWPFormsのようなプラグインを入れるのが定番だと思います。

ただ、今回のようにページ数の少ない小規模サイトでは、フォーム1つのためにプラグインを追加すると、その分の管理画面の複雑さや将来的なアップデート対応、プラグイン間の競合リスクが増えてしまいます。

送信処理自体はシンプルなメール送信なので、今回はテーマのfunctions.phpに直接処理を書いて自作することにしました。

admin_postフックで送信処理を受け止める

自作フォームの送信先には、WordPressが用意しているadmin-post.phpという仕組みを使います。

add_action( 'admin_post_nopriv_wanko_to_contact', 'wanko_to_handle_contact_form' );
add_action( 'admin_post_wanko_to_contact', 'wanko_to_handle_contact_form' );

admin_post_{action名}はログイン済みユーザーからの送信、admin_post_nopriv_{action名}は未ログイン(一般の訪問者)からの送信を受け止めるフックです。

一般公開のお問い合わせフォームなので、訪問者はほぼ確実に未ログイン状態のため、この2つを両方登録しておく必要があります。

フォーム側は、送信先をadmin-post.phpにして、actionという名前の隠しフィールドにwanko_to_contactという値を入れておくだけです。

nonceでCSRF対策をする

フォーム処理を自作する以上、CSRF(クロスサイトリクエストフォージェリ)対策は自分で入れる必要があります。

WordPressにはwp_nonce_field()wp_verify_nonce()という、これ専用の仕組みが用意されています。

function wanko_to_handle_contact_form() {
  if ( ! isset( $_POST['wanko_to_contact_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['wanko_to_contact_nonce'] ) ), 'wanko_to_contact' ) ) {
    wp_safe_redirect( add_query_arg( 'contact', 'error', home_url( '/#contact' ) ) );
    exit;
  }

  // ...
}

フォーム表示時にwp_nonce_field( 'wanko_to_contact', 'wanko_to_contact_nonce' )でトークンを埋め込んでおき、送信処理の一番最初でこのトークンを検証しています。

検証に失敗した場合は、内容を処理せずにそのままフォームへリダイレクトして終了させています。

ハニーポットで簡易的なスパム対策をする

nonce検証だけでも多少のスパム対策にはなりますが、今回はもう一段階、ハニーポットという手法も入れました。

// ハニーポット(ボット対策)。人間には見えない項目に入力があればスパムとみなす。
if ( ! empty( $_POST['wanko_to_contact_website'] ) ) {
  wp_safe_redirect( add_query_arg( 'contact', 'success', home_url( '/#contact' ) ) );
  exit;
}

これは、CSSで画面上には見えないようにした入力欄をフォームにこっそり用意しておき、そこに何か入力されていたら人間ではなくボットによる自動送信だとみなす、という手法です。

人間の訪問者には見えないので普通に使う分には空欄のまま送信され、機械的にフォーム項目を全部埋めて送信してくるタイプのスパムボットだけが引っかかる仕組みです。

見た目上は成功したように見せかけつつ実際にはメールを送らずに処理を打ち切っている点がポイントで、スパムボット側に「失敗した」と気づかれて別の回避策を試されるのを防ぐ意図もあります。

メール送信と結果の受け渡し

nonceとハニーポットのチェックを抜けたら、あとは各項目をサニタイズしたうえでwp_mail()で管理者宛にメールを送るだけです。

$sent = wp_mail( get_option( 'admin_email' ), '【おうちで、わんこと。】お問い合わせ', $body, array( 'Reply-To: ' . $email ) );

wp_safe_redirect( add_query_arg( 'contact', $sent ? 'success' : 'error', home_url( '/#contact' ) ) );
exit;

admin-post.phpはリダイレクトで処理を終えるのが基本の流れなので、送信結果を?contact=successのようなクエリパラメータに乗せてフォームのページへ戻し、フロント側でその値を見て成功・失敗メッセージを出し分けています。

まとめ

今回は、小規模サイトのお問い合わせフォームをプラグインなしで自作し、admin-post.phpフックでの受け口作り、nonceによるCSRF対策、ハニーポットによる簡易スパム対策を実装した話を書きました。

大掛かりなプラグインを入れなくても、WordPress標準の仕組みだけで最低限のセキュリティを備えたフォームは十分作れると感じています。

同じように小規模サイトでフォームだけのためにプラグインを増やすのに迷っている方の参考になればうれしいです。

XServerへのWordPress本番デプロイでハマった話

フリーランスでWeb制作をしている矢野です。

前回・前々回の記事で、このポートフォリオサイトのテーマ自作とローカル開発環境の構築について書きま した。

今回はその続きで、実際にXServerへ本番デプロイしたときにハマった3つのポイントをまとめます。

デプロイ全体の流れ

まず全体像から書いておきます。

今回のデプロイは、大きく分けて次の流れで進めました。

  1. ドメインをサーバーパネルに追加
  2. 無料独自SSLを設定
  3. WordPressの簡単インストール
  4. FTPでテーマをアップロード
  5. コンテンツ(固定ページ・投稿)の移行

言葉にすると簡単そうに見えますが、実際には最初の2ステップだけでかなりの時間を溶かしました。

ハマったポイント①:「無料独自SSLの設定に失敗しました」の正体

ドメインをサーバーパネルに追加した直後、無料独自SSLの設定が失敗しました。

エラーメッセージには「DNS参照先サーバーが当サーバーではない」という趣旨の内容が表示されます。

これを見て、真っ先にネームサーバーの設定ミスを疑いました。

ところが確認してみると、ネームサーバーは最初からXServerのものを正しく向いていたんです。

では何が原因だったのかというと、サーバーパネル側での「ドメイン設定の追加」という、別の手順が抜け ていました。

ここで整理しておきたいのが、次の2つは似ているようで別物だという点です。

ネームサーバーが正しくても、後者の紐付け作業をしていなければ、SSLの発行対象となるサーバーが特定で きず失敗する、という仕組みのようです。

サーバーパネルの「ドメイン設定」から追加を済ませたところ、無事にSSLも反映されました。

ハマったポイント②:wp-config.phpで安全にhttps化する

SSLの設定が終わったら、次はWordPress側のURLをhttpsに変更する作業です。

実は以前、別のタイミングで「設定」→「一般」の画面から直接WordPressアドレスをhttpsに変更し、管理画 面にログインできなくなった経験がありました。

そのため今回は、より安全な方法を選びました。

wp-config.phpに、以下の2行を追記する方法です。

define('WP_HOME','https://yanoworks.com');
define('WP_SITEURL','https://yanoworks.com');

この方法のメリットは、データベースを一切触らずに済むことです。

万が一おかしくなった場合も、FTPでこの2行を削除するだけで元の状態に戻せます。

管理画面のUIから変更する方法に比べて、はるかに復旧しやすい手段だと感じました。

ハマったポイント③:テーマの二重フォルダ問題

テーマをFTPでアップロードする際、zipファイルをファイルマネージャーで解凍する方法を取りました。

このとき、アップロード先に同名のフォルダをあらかじめ作っていたことが原因で、次のような二重のフォ ルダ構造になってしまいました。

wp-content/themes/yano-works-theme/yano-works-theme/style.css

本来であればwp-content/themes/yano-works-theme/style.cssが正しい構造です。

一見、これではテーマとして認識されないのではと不安になりましたが、実際にサイトを開くと問題なく表 示されていました。

調べてみると、WordPressのテーマスキャナーは、直下にstyle.cssが見つからない場合、1階層深いフォルダ も探索してくれる仕組みになっているようです。

実害はなかったものの、気づかずに放置していると後々の管理で紛らわしくなるので、テーマをアップロー ドする際はフォルダ構造を一度確認しておくことをおすすめします。

まとめ:本番デプロイ前チェックリスト

今回の経験をもとに、本番デプロイ時に確認すべきポイントをチェックリストにしておきます。

デプロイ自体は難しい作業ではありませんが、こうした小さな見落としの積み重ねで想定より時間がかかり ました。

同じようにXServerへのデプロイを控えている方の参考になれば嬉しいです。

wp-env(Docker)でWordPressのローカル開発環境を構築した話

フリーランスでWeb制作をしている矢野です。

前回の記事で、ポートフォリオサイトのWordPressオリジナルテーマを自作した話を書きました。

今回はその開発環境の話で、Local by FlywheelのようなGUIツールではなく、Docker製のwp-env(@wo rdpress/env)でローカル環境を構築した経緯とハマったポイントをまとめます。

なぜLocalではなくwp-env(Docker)を選んだか

WordPressのローカル開発環境というと、Local by Flywheelのような専用GUIツールを使う人が多いと思います。

私も最初はそちらも検討しました。

ただ、今回はあえてDockerベースのwp-envを選びました。

理由はシンプルで、Dockerに慣れておくことが今後の現場での仕事やエンジニアとしてのスキルに直結する と考えたからです。

WordPress専用のGUIツールに慣れても、その知識は他の環境に転用しにくいですが、Dockerの経験はどんな 案件でも活きてきます。

もう1つの理由は、@wordpress/envがWordPress公式が提供するツールだという安心感です。

サードパーティのツールに依存するよりも、公式ツールに乗っておいた方が長く付き合えると判断しました 。

セットアップ手順

セットアップ自体はシンプルです。

まずDocker Desktopをインストールし、起動しておきます。

続いて、テーマのディレクトリで@wordpress/envをインストールします。

npm install --save-dev
@wordpress/env

テーマ開発用の設定は、.wp-env.jsonに記述します。

私の場合は以下のようなシンプルな設定にしました。

{
  "core": null,
  "phpVersion": "8.2",
  "themes": ["."],
  "config": {
    "WP_DEBUG": true
  },
  "testsEnvironment": false
}

themes"."を指定することで、今作業しているディレクトリそのものをテーマ としてWordPressにマウントしてくれます。

あとは起動コマンドを叩くだけです。

npx @wordpress/env start

これでhttp://localhost:8888にWordPressの環境が立ち上がります。

ハマったポイント①:「wp-env」という名前の罠

ここが一番最初につまずいたポイントです。

セットアップ後、何気なく次のコマンドを実行しました。

BEFORE(うまくいかなかったコマンド)

npx wp-env start

一見動いているように見えるのですが、これは@wordpress/envとは無関係の、たまたま同じ「 wp-env」という名前を持つ別のnpmパッケージが実行されていました。

しばらく気づかずに操作していて、意図した挙動にならず首をかしげる時間を過ごしてしまいました。

AFTER(正しいコマンド)

npx @wordpress/env start

パッケージのスコープ(@wordpress/)まで含めてフルネームで実行しないといけない、とい う当たり前のことに気づくまで、少し時間がかかりました。

以降は必ず@wordpress/envとスコープ付きで実行するように徹底しています。

ハマったポイント②:Docker Desktopが重くなる・起動しない

開発を進めている途中、突然Docker Desktopが重くなり、Macのファンが唸るほどCPUを使い切ってしまったことがありました。

調べてみると、com.docker.backendというプロセスが暴走していたことが原因でした。

このプロセスを一度終了させ、Docker Desktopを再起動することで復旧しました。

また別の日には、Docker Desktop自体が起動しない状態にも遭遇しています。

このときは、画面の裏に隠れていたサブスクリプション案内のモーダルが、起動処理をブロックしていたこ とが原因でした。

利用規約への同意とセットアップウィザードを完了させることで、無事に起動できるようになりました。

ハマったポイント①:「wp-env」という名前の罠

ここが一番最初につまずいたポイントです。

セットアップ後、何気なく次のコマンドを実行しました。

BEFORE(うまくいかなかったコマンド)

npx wp-env start

一見動いているように見えるのですが、これは@wordpress/envとは無関係の、たまたま同じ「 wp-env」という名前を持つ別のnpmパッケージが実行されていました。

しばらく気づかずに操作していて、意図した挙動にならず首をかしげる時間を過ごしてしまいました。

AFTER(正しいコマンド)

npx @wordpress/env start

パッケージのスコープ(@wordpress/)まで含めてフルネームで実行しないといけない、とい う当たり前のことに気づくまで、少し時間がかかりました。

以降は必ず@wordpress/envとスコープ付きで実行するように徹底しています。

ハマったポイント②:Docker Desktopが重くなる・起動しない

開発を進めている途中、突然Docker Desktopが重くなり、Macのファンが唸るほどCPUを使い切ってしまったことがありました。

調べてみると、com.docker.backendというプロセスが暴走していたことが原因でした。

このプロセスを一度終了させ、Docker Desktopを再起動することで復旧しました。

また別の日には、Docker Desktop自体が起動しない状態にも遭遇しています。

このときは、画面の裏に隠れていたサブスクリプション案内のモーダルが、起動処理をブロックしていたこ とが原因でした。

利用規約への同意とセットアップウィザードを完了させることで、無事に起動できるようになりました。

Dockerを使った開発では、こうしたアプリ自体の不調にも向き合う必要がある、という点は覚えておいて損 はないと思います。

ハマったポイント③:カスタム投稿タイプのURLが404になる

テーマ側でカスタム投稿タイプ(work)を新しく登録したときのことです。

投稿の一覧・詳細ページのURLにアクセスすると、404エラーが表示されてしまいました。

原因は、カスタム投稿タイプを追加した際にパーマリンクのリライトルールが更新されていなかったことで した。

wp-adminの「設定」→「パーマリンク設定」を開いて保存し直すか、wp-cliで直接反映させることで解決でき ます。

npx @wordpress/env run cli wp rewrite
flush

このコマンド1つで直りますが、原因を知らないと地味に時間を溶かしてしまうポイントだと思います。

まとめ

Docker経験がゼロの状態から始めましたが、今回まとめた3つのハマりポイントさえ押さえておけば、大きく つまずかずに環境構築できるはずです。

次回は、本番環境(XServer)へのデプロイでハマった話を書く予定です。

WordPressオリジナルテーマを一から自作してポートフォリオサイトを作った話

フリーランスでWeb制作をしている矢野です。

今回、この技術ブログを含むポートフォリオサイト(yanoworks.com)そのものを、購入テーマやページビル ダーを使わず、WordPressのオリジナルテーマとして一から自作しました。

この記事では、「なぜ自作したか」「どう設計したか」「どこでハマったか」を、実際の試行錯誤も含めて 書いておきます。

なぜ購入テーマではなく自作したか

正直、購入テーマやページビルダーを使えば、もっと早く形にできたと思います。

それでもオリジナルテーマにこだわったのには理由があります。

1つ目は、単純に技術力を直接示せるからです。

ポートフォリオサイト自体が購入テーマだと、「このサイト、テンプレートっぽいな」と思われた時点で説 得力が落ちてしまいます。

逆に、サイト自体が自分の実装力の証明になるなら、これほど分かりやすいアピールはありません。

2つ目は、カスタム投稿タイプやカスタムフィールドを使った柔軟な設計が、既製テーマだと窮屈に感じたこ とです。

実績(work)をどう管理するか、フォームの項目をどう増やすか、といった細かい調整を自分の裁量で決め られるのは、自作ならではのメリットだと感じています。

設計の全体像

構成としては、大きく分けて2つの独自実装があります。

1つ目は、実績管理用のカスタム投稿タイプ「work」です。

クライアント名・サイトURL・タグをカスタムフィールドとして持たせ、実績一覧はこのカスタム投稿タイプ をループして表示する形です。

実績が増えても、投稿を追加するだけで一覧・詳細ページが自動的に増えていく設計です。

2つ目は、プラグインを使わない自作のお問い合わせフォームです。

admin-post.phpのフックを使い、nonceによるCSRF対策、ハニーポットによるスパム対策、wp_mail()による メール送信までを自前で実装しています。

プラグインに頼らず自作したのは、「自分で作れる」という一貫性を持たせたかったからです。

デザインで意識したこと:「AI感」を消す試行錯誤

正直、ここが一番時間をかけた部分です。

最初に組んだデザインは、実績カードに枠線・影・ブラウザ風のモックアップフレームをつけた、いわゆる 「よくあるテンプレート的な」見た目でした。

ただ自分で見返すと、生成AIが作ったようなテンプレ感が強く、何度も作り直す羽目になりました。

最終的に実績セクションは、カード型をやめて、連番とセリフ体タイトルを使った編集誌風のリスト表示へ と作り替えています。

枠や影に頼らず、タイポグラフィだけで情報の階層を作る方向です。

もう1つ判断に迷ったのが、スキルセットの見せ方です。

最初はスキルの習熟度をドットやバーで可視化する案を検討していました。

ただ、これは依頼者目線で考えたときにリスクがあると気づいたんです。

「バーが100%じゃないから、この人に頼むのはやめておこうか」と、非技術者の依頼者に余計な不安を与え てしまう可能性があるからです。

信頼構築が目的のセクションでは、定量的な自己評価よりもプレーンな一覧の方が適切だと判断し、最終的 には装飾のない連番リストに落ち着きました。

つまずいたポイント

FAQアコーディオンのアニメーションが不安定だった

開閉のアニメーションを、最初はCSSのgrid-template-rowsを0frから1frに変える方法で実装していました。

BEFORE(CSSのみで実装)

.faq-item .faq-answer {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms ease;
}

.faq-item[open] .faq-answer {
  grid-template-rows: 1fr;
}

ただ、閉じるときの動きが不安定だったり、特定の項目だけ挙動がおかしくなったりして、原因の切り分け に時間がかかりました。

最終的には、JavaScriptで開閉前後の実際の高さを測定し、Web Animations APIで直接アニメーションさせる方式に切り替えています。

AFTER(実測した高さをWeb Animations APIでアニメーション)

const toggleFaq = (item) => {
  const answer = item.querySelector('.faq-answer');
  const height = measureOpenHeight(answer);

  item.animate(
    [{ height: '0px' }, { height: height + 'px' }],
    { duration: 300, easing: 'ease' }
  );
};

こちらに切り替えたことで、安定した開閉動作を実現できています。

レスポンシブでCSS Gridの列数が想定より減っていた

スマートフォン表示で、2列になるはずのグリッドが1列になってしまう不具合がありました。

BEFORE(余白を考慮していなかった指定)

.flow-steps {
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 2rem;
}

原因を追ったところ、親要素の.sectionと.containerの両方に左右の余白(パディング)が設定されており 、想定より表示エリアが狭くなっていたことが分かりました。

AFTER(スマホ幅では最小値と余白を縮小)

@media (max-width: 900px) {
  .flow-steps {
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
    gap: 1rem;
  }
}

grid-template-columns: repeat(auto-fit, minmax(150px, 1fr))のような指定は、余白を含めた実際の表示幅次第で列数が変わるため、レイアウトの入れ子構造まで含め て計算する必要がある、という学びがありました。

まとめ

購入テーマを使えばもっと早く公開できたと思いますが、自作したことで、「自分が何を作れるか」を一番 説得力のある形で示せたと感じています。

特に、「AI感をどう消すか」「依頼者にとって不要な不安を与えない見せ方は何か」といった判断は、実際 に手を動かして初めて気づけたことでした。

これから副業や独立でWeb制作を始めようとしている方の参考になれば嬉しいです。

次回は、このサイトのローカル開発環境をDocker(wp-env)で構築した話を書く予定です。