ソース解説(5) 文字列を自分で組み立てる場所
このサイトで、HTMLを文字列として自分で組み立てている場所は2つあります。RSSフィードとサイトマップです。
画面に出るページは、部品を組み合わせれば勝手に出来上がります。この2つは違います。1行ずつ並べて、最後に文字列としてつなぎます。
第5回。前回:速くしたら、デプロイが止まった 次回:スクロールを見る場所を1つにまとめた(近日公開)
自分で組み立てる場所に何が必要になるか。具体的には、エスケープ、日付の形式、そして省略していい項目とそうでない項目の見分け方です。
1文字でフィード全体が壊れる
まずここから。記事のタイトルに「&」や「<」が入ることは普通にあります。
| 順番 | 元の文字 | 書き出す形 |
|---|---|---|
| 1 | & |
& |
| 2 | < |
< |
| 3 | > |
> |
| 4 | 二重引用符 | " |
| 5 | 単引用符 | ' |
これを通さずにタイトルを書き込むと、XMLとして読めなくなります。
HTMLは多少おかしくてもブラウザが解釈してくれますが、XMLは1か所でも壊れると全体が読めません。
つまり、タイトルに「&」が入った記事を1本出した瞬間に、購読アプリ側では何も表示されなくなります。その1本だけが抜けるのではありません。
そして & の置き換えがいちばん最初にあるのが重要です。後ろに回すと、< を < に変えた後の & をもう一度変換してしまい、&lt; になります。
日付の名前を、自分で持っている
RSSが定める日付は「Sun, 24 Aug 2026 13:27:40 GMT」の形です。英語の曜日と月名を使います。
const DAYS = ["Sun", "Mon", "Tue", ...];
const MONTHS = ["Jan", "Feb", "Mar", ...];
日付を文字にする機能はブラウザにもありますが、結果が端末の言語設定に左右されます。日本語の環境で「日」「8月」と出てしまうと、フィードとしては読めません。
形式が決まっているものは、こちらで名前を持っているほうが確実でした。
時間帯の表記が無い文字列
WordPressから届く日付は「2026-08-24T13:27:40」の形です。末尾に時間帯の表記がありません。
const iso = HAS_TIMEZONE.test(gmt)
? gmt
: gmt + "Z";
すでに時間帯が付いているかを確かめて、無ければ Z(世界標準時)を足してから解釈します。
HAS_TIMEZONE は、末尾に時間帯の表記があるかを判定する正規表現です。実物は1行に書いてありますが、長くて読む邪魔になるので、ここでは名前を付けて抜き出しています。
時間帯の表記が無い文字列は、解釈する側の地域として扱われることがあります。日本で動かすと9時間ずれた値になります。
表示は普通に出ます。「少し前の日付だな」と思うだけで、ずれていることに気づけません。
無い項目は、書かない
組み立てる側でいちばん判断が要るのがここでした。
pubDate ? "<pubDate>" + pubDate + "</pubDate>" : null,
summary ? "<description>" + summary + "</description>" : null,
そして最後に、null の行を落としてからつなぎます。
.filter((line): line is string => line !== null)
.join("\n")
日付や説明が取れなかったとき、空のまま項目だけ書くこともできます。そうしないのは、空の項目が「値が無い」ではなく「空という値がある」と読まれるからです。
サイトマップ側でも同じ判断をしています。
if (post.modified) {
entry.lastModified = new Date(post.modified);
}
更新日が取れないときに今日の日付を埋めると、「毎日更新されている」と伝えることになります。
形としては揃いますが、内容は嘘です。何度か見に来られて、毎回変わっていないと分かれば、そのうち信用されなくなります。書かないほうが正確でした。
更新日時に、生成時刻を使わない
フィード全体の更新日時も同じ考え方です。
const lastBuildDate = toRfc822(posts[0]?.dateGmt);
作った瞬間の時刻ではなく、いちばん新しい記事の日付を使っています。
生成時刻にすると、中身がまったく変わっていなくても毎回新しく見えます。フィードは1時間ごとに作り直しているので、記事を1本も書いていない日でも「24回更新された」ことになります。
作り直す間隔を、用途で分けている
このサイトには、作り直す間隔が3種類あります。
| 対象 | 間隔 | 理由 |
|---|---|---|
| 記事ページ・一覧 | 5分 | 読む人が直接見る |
| RSS | 1時間 | 購読アプリ側も数十分おきにしか来ない |
| サイトマップ | 1時間 | 載っているかだけが問題 |
RSSを5分にしても、届く速さは変わりません。相手が見に来る間隔のほうが長いからです。短くして得があるのは、読む人が直接開くページだけでした。
サイトマップは、XMLを書いていない
最後に1つ。フィードは文字列を組み立てていますが、サイトマップは違います。
export default async function sitemap():
Promise<MetadataRoute.Sitemap> {
return [...staticEntries, ...glossaryEntries, ...postEntries];
}
配列を返すだけで、XMLは自動で作られます。RSSに同じ仕組みが無いので、そちらだけ手で書いています。
手で書ける場所では、手で書かない。エスケープも日付の形式も、自分で持てば自分で間違えられるようになります。
今回の学び
まず、厳しい形式ほど、壊れ方が全か無かになるということ。XMLは1文字で全体が読めなくなります。多少おかしくても表示されるHTMLに慣れていると、この差は見落とします。
次に、値が無いときに、形を揃えるために埋めないこと。更新日が無いのに今日の日付を入れると、形は整いますが意味が変わります。空欄のまま出すか、項目ごと書かないかを選ぶほうが正確でした。
そして、間隔は相手の都合で決めること。購読アプリが1時間おきにしか来ないなら、こちらが5分ごとに作り直しても誰も早く受け取れません。速くする対象を選ぶのも、相手が誰かを見てからでした。
この記事を共有する
関連記事

ソース解説(4) 速くしたら、デプロイが止まった
記事のデータは、共用サーバー上のWordPressから取ってきています。この部分は2回作り直しました。 最初は「常に最新を取る」形。速度の問題が出て、キャッシュする形へ。すると今度は、デプロイが止まるようになりました。 […]

そのページは何だと名乗っているか
用語集の分類ページを調べていて、妙な指定に気づきました。このページは「記事」だと名乗っていました。 中身は用語が並んだ一覧です。公開日もなければ、書いた人もいません。それでも、SNSや検索エンジンに向けては「これは1本の […]

見た目で区別が付いているものは、機械には区別が付いていない
見た目で伝わっている区別が、機械には伝わっていなかった話です。何が起きていたか、どう直したか、そして「見えるから伝わっている」という思い込みについて書きます。