ノウハウ サイト運営 読了目安 約5分

WordPressなしで記事を予約公開する|PHPと日付だけで作る仕組み

このサイトはWordPressを使わず、PHPのファイルを並べて作っています。それでも「毎日1本ずつ記事を公開する」運用ができるよう、日付だけで予約公開する仕組みを組みました。cronも管理画面も使わない、シンプルな方法です。

考え方:「未来の日付の記事は存在しないことにする」

予約公開というと、決まった時刻にファイルを移動したり、データベースの状態を書き換えたりする処理を想像するかもしれません。しかし、もっと単純な方法があります。

記事はすべて最初からサーバーに置いておき、ページを表示するたびに「今日の日付」と「記事の公開日」を比べる。公開日が未来なら、その記事は一覧にも出さず、URLに直接アクセスされても「見つかりません」と返す。これだけです。

この方法なら、決まった時刻に何かを実行する仕組み(cron)が要りません。日付が変わった瞬間から、次にページが表示されたときに自動で記事が現れます。

記事フォルダの構成

このサイトでは、1記事を1フォルダにまとめています。

howto/articles/記事のスラッグ/
├── meta.php   … タイトル・説明・公開日などの情報
├── index.php  … 共通レイアウトを呼び出すだけの入口
└── body.php   … 本文のHTML

ポイントは、記事の情報(メタ情報)を本文と分けて meta.php に持たせていることです。一覧ページは本文を読まずにメタ情報だけを集めればよいので、記事が増えても処理が軽く済みます。

<?php
return [
  'title'     => '記事のタイトル',
  'description' => '一覧や検索結果に出る説明文',
  'date'      => '2026-10-07',
  'url'       => '/howto/articles/記事のスラッグ/',
  'published' => true,
];

return で配列を返す書き方にしておくと、読み込む側は $meta = require 'meta.php'; の1行で情報を受け取れます。

一覧ページ:公開日で絞り込む

一覧ページでは、記事フォルダを順に見て回り、条件に合う記事だけを集めます。

$today = (new DateTime('now', new DateTimeZone('Asia/Tokyo')))->format('Y-m-d');
$items = [];

foreach (scandir($articlesDir) as $slug) {
  if ($slug[0] === '.' || $slug[0] === '_') continue;
  $metaFile = "$articlesDir/$slug/meta.php";
  if (!is_file($metaFile)) continue;

  $meta = require $metaFile;
  if (empty($meta['published'])) continue;      // 下書き
  if ($meta['date'] > $today) continue;         // 予約中

  $items[] = $meta;
}

usort($items, fn($a, $b) => strcmp($b['date'], $a['date']));

日付を 2026-10-07 のような「年-月-日」の形で統一しておくと、文字列のまま大小を比べられます。桁数がそろっているため、文字列の比較と日付の前後が一致するからです。

ここで大切なのがタイムゾーンの指定です。サーバーの時計が世界標準時(UTC)で動いていると、日本時間の0時ではなく午前9時に記事が公開されてしまいます。Asia/Tokyo を明示しておけば、サーバーの設定に左右されません。

記事ページ:公開前は404を返す

一覧に出さないだけでは不十分です。URLを推測されたり、テスト中に誤ってリンクを貼ったりすると、公開前の記事が読めてしまいます。記事ページ側でも同じ判定をして、公開日前なら404を返します。

$meta = require __DIR__ . '/meta.php';
if (empty($meta['published']) || $meta['date'] > $today) {
  http_response_code(404);
  readfile(__DIR__ . '/../../../404.html');
  exit;
}

この判定は、HTMLを1文字でも出力する前に行う必要があります。先に出力が始まっていると、ステータスコードを404に変えられないためです。

このサイトでは、この判定と、ヘッダー・本文・フッターの組み立てを共通ファイルにまとめ、各記事の index.php はそれを呼び出すだけにしています。レイアウトを直したくなっても、共通ファイルを1つ直せば全記事に反映されます。

本文ファイルの直接アクセスを防ぐ

body.php は本文の断片なので、単体で開かれると見た目の崩れたページが表示されてしまいます。共通レイアウトから読み込んだときだけ中身を出すよう、先頭に目印のチェックを入れています。

<?php if (!defined('KH_DAILY_ARTICLE')) { http_response_code(404); exit; } ?>

共通レイアウト側で define('KH_DAILY_ARTICLE', true); を実行してから本文を読み込むので、正規の経路以外では404になります。

サイトマップも自動で更新する

記事が公開されても、検索エンジンに知らせるサイトマップが古いままでは発見が遅れます。かといって、毎日サイトマップを作り直してアップロードするのは手間です。

そこで、サイトマップもPHPで動的に生成し、一覧ページと同じ条件で公開済みの記事だけを出力するようにしました。.htaccess で sitemap-daily.xml へのアクセスをPHPに振り向けています。

RewriteRule ^sitemap-daily\.xml$ sitemap-daily.php [L]

あとは robots.txt とSearch Consoleにこのサイトマップを登録しておけば、公開日を迎えた記事が自動的にサイトマップへ載っていきます。

この方法の注意点

  • キャッシュ:サーバーやCDNでHTMLをキャッシュしていると、日付が変わっても古い一覧が表示されることがあります。HTMLはキャッシュしない、または短時間にする設定が必要です。
  • 記事間のリンク:先の日付の記事へリンクを貼ると、公開日まではリンク切れ(404)になります。リンクは公開済みの記事に向けて貼るのが安全です。
  • 公開前の確認方法:自分だけ公開前の記事を確認したい場合は、秘密の文字列をURLに付けたときだけ表示し、その際は検索エンジンに登録されないよう noindex を付ける、といった仕組みを用意しておくと便利です。

データベースも管理画面もない仕組みですが、「記事はファイル、公開は日付で判定」と役割を分けることで、十分に予約公開の運用ができます。書きためた記事をまとめてアップロードしておけば、あとは毎日自動で1本ずつ公開されていきます。