Skip to content

Write valid URLs, hreflang codes and dates in every sitemap - #2

Merged
czj merged 2 commits into
mainfrom
valid-xml-and-hreflang
Nov 12, 2026
Merged

czj merged 2 commits into
mainfrom
valid-xml-and-hreflang

Conversation

@czj

@czj czj commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

A URL built by hand could put a space, an accent or a control character in a file, and one such URL made the whole file unreadable. Rails locales such as pt_BR went out as hreflang="pt_BR", which search engines ignore. A lastmod string was written whatever it held, an impossible date crashed the build, and the index took the greatest string rather than the latest time. x_default was missed when the locales block returned strings.

URLs are now percent-encoded outside RFC 3986's characters, locales are written as hreflang codes, and lastmods are validated as W3C dates and written in UTC. The check reports what search engines would reject or ignore.

  • Percent-encoded URLs: spaces, accents, control characters, invalid UTF-8 and a lone % are encoded byte by byte; a protocol-relative URL (//cdn…) takes the scheme of the host, not the host.
  • Hreflang codes: pt_BR is written pt-BR, x_default is found whatever the type of the locales, and two languages sharing a URL list it once.
  • W3C dates: a lastmod is read once per page, impossible dates are left out, offsets are converted to UTC, and a date alone counts from UTC+14, so today is never in the future.
  • More checks: URLs over 2,048 characters, hreflang values that are not language codes, an index over 50,000 files, and, once per source, a lastmod that is not a date or a page whose languages all share one URL.
Proof

55 runs, 149 assertions, green on Rails 8.1 and 8.0; RuboCop clean. New tests cover each case above, including a URL holding Café\u0001 d\xE9j\xE0 and 100%, the locales pt_BR and en_GB, the dates 2026-13-01, 2026-02-30 and 2026-01-15T10:00+09:00, and a date on the first day on Earth.

bench/build.rb (30,000 records × 24 languages, Ruby 3.3.6 without YJIT): full build 30.2 s and 31.7 s on this branch, 26.9 s, 30.9 s and 33.8 s on main. The spread on main alone is wider than the difference.

  • Tests cover the change (a bug fix comes with a test that fails without it)
  • Output unchanged, or every difference is intended and listed above

Generated by Claude Code

A URL built by hand could put a space, an accent or a control character
in a file, which made it unreadable. Rails locales such as pt_BR went out
as hreflang="pt_BR", which search engines ignore. A lastmod string was
written whatever it held, and the index took the greatest string rather
than the latest time. x_default was missed when the locales block returned
strings, and two languages sharing a URL listed it twice.

URLs are now percent-encoded outside RFC 3986's characters, locales are
written with a hyphen, lastmods are read as W3C dates and written in UTC,
and a page lists each of its URLs once. The check reports URLs over 2,048
characters, hreflang values that are not language codes, lastmods that are
not dates, and an index with no file or more than 50,000.

Assisted-by: Claude Code
An impossible date such as 2026-13-01 crashed the build, and 2026-02-30
was written out. A date alone counted from UTC midnight, so today's date
looked like the future east of UTC. A url block that forgot the locale no
longer warned once its URLs were listed once per page. A protocol-relative
URL got the host in front, and a % not followed by two hex digits stayed.

Dates are validated, a date alone starts at UTC+14, a page whose languages
share one URL is reported once per source, as is a lastmod that is not a
date, and the lastmod is read once per page. An empty site no longer fails
the check.

Assisted-by: Claude Code
@czj
czj enabled auto-merge (squash) November 12, 2026 07:48
@czj
czj merged commit 2d4399a into main Nov 12, 2026
10 of 11 checks passed
@czj
czj deleted the valid-xml-and-hreflang branch November 12, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants