EmDash trailing-slash sitemap policy proposal
Status and scope
This is a local, upstream-ready issue/PR draft based solely on the installed
emdash@0.28.1 source. It proposes no Hikaridia deployment, project sitemap
replacement, node_modules patch, or dependency upgrade.
Problem statement
An Astro site may intentionally make its public canonical and hreflang URLs
end in /. Hikaridia does this through normalizeTrailingSlash() in
src/i18n/ui.ts:636 and uses it for canonical URLs in
src/layouts/Base.astro:87 and locale alternates in src/layouts/Base.astro:78.
EmDash collection sitemaps currently generate the same content URL without a
trailing slash. In installed emdash@0.28.1:
src/astro/routes/sitemap-[collection].xml.ts:98-108 resolves every entry
from its collection url_pattern, then localizes it before emitting <loc>;
src/i18n/resolve.ts:49-65 (interpolateUrlPattern) removes a terminal /;
src/i18n/resolve.ts:92-98 calls normalizePath; and
src/i18n/resolve.ts:184-189 removes a terminal / again.
Consequently, a canonical such as
/en/blog/how-to-verify-lab-grown-diamond-report/ can disagree with the
collection sitemap location /en/blog/how-to-verify-lab-grown-diamond-report.
The mismatch affects sitemap <loc> and sitemap hreflang alternates.
Minimal backward-compatible API/design
Add an explicit optional sitemap URL policy to the EmDash Astro integration:
emdash({
sitemap: { trailingSlash: "preserve" }, // default remains "never"
});
Semantics:
"never" is the default and preserves exactly the current 0.28.1
no-trailing-slash output.
"preserve" retains a terminal slash deliberately supplied by a collection
urlPattern, including after locale-prefix construction.
- This setting applies only to collection sitemap content URLs and their
sitemap hreflang / x-default alternates. It does not change routing,
canonical generation, redirects, media URLs, robots, or sitemap-index URLs.
- The collection remains responsible for expressing policy, for example
/blog/{slug}/; patterns without a terminal slash stay slashless even under
"preserve".
This makes output explicit, preserves existing installations by default, and
avoids inferring a site-wide routing policy from Astro internals.
File-level upstream patch plan (not applied)
packages/core/src/astro/integration/runtime.ts and the public EmDash
config types: add and validate sitemap.trailingSlash?: "never" | "preserve";
default it to "never".
packages/core/src/i18n/resolve.ts: add an explicit resolver option that
permits preservation of a terminal slash. It must retain the current default
behavior for all existing callers.
packages/core/src/astro/routes/sitemap-[collection].xml.ts: pass that
option only while resolving sitemap content URLs and all sibling/x-default
alternates. Do not apply it to buildSeoImageUrl().
packages/core/src/astro/routes/sitemap.xml.ts: no functional URL change;
retain child sitemap index URLs without trailing slashes.
- EmDash configuration and sitemap documentation: document default behavior,
the preserve mode, and the requirement that a collection urlPattern
itself ends in / to opt in.
Test matrix
Run each case with trailingSlash: "never" and "preserve". In preserve
mode, use terminal-slash patterns for the positive cases and a no-slash pattern
for the compatibility case.
| Collection / data |
Locales |
Assertions |
posts, published and indexable |
ja, en, zh |
<loc> equals the intended canonical URL; locale prefix rules are retained; all sitemap hreflang values and x-default equal their matching canonical forms. |
diamonds, published and indexable |
ja, en, zh |
Same URL and alternate assertions; proves the policy is collection-generic. |
| Future SEO-enabled collection |
ja, en, zh |
Discovery through has_seo = 1, URL pattern interpolation, and locale output work without a route-specific allowlist. |
| Translation group with image and timestamp |
ja, en, zh |
Preserve <image:loc> exactly, emit W3C <lastmod>, retain all sibling alternates and x-default. |
| Excluded rows |
any |
Draft, deleted, and seo_no_index = 1 rows remain absent. |
| Compatibility |
any |
Default and explicit never retain no-trailing-slash collection sitemap URLs; preserve mode does not add / when the pattern has none. |
Integration assertions:
- Rendered document canonical/hreflang and matching sitemap entry are byte-equal
URLs for each representative route.
- Root sitemap index retains its
sitemap-{collection}.xml child URLs.
- Existing collection eligibility, 50,000-entry cap, image entries, XML
escaping, error responses, cache headers, and locale-unroutable exclusion
remain unchanged.
Installed-source evidence
Installed source (emdash@0.28.1) |
Evidence |
src/api/handlers/seo.ts:98-187 |
Selects every has_seo = 1 collection, retaining only published, non-deleted, indexable rows and returning url_pattern, locale, translation group, image, and timestamps. |
src/astro/routes/sitemap-[collection].xml.ts:45-201 |
Renders per-collection sitemap XML, images, sibling hreflang, and x-default. |
src/i18n/resolve.ts:49-65 |
Interpolates {slug}/{id} and strips terminal slash. |
src/i18n/resolve.ts:92-98,184-189 |
Localizes a path and normalizes it by stripping terminal slash. |
src/astro/integration/routes.ts:65-72,793-805 |
Provides explicit project-route bypass for root sitemap.xml, but unconditionally injects the collection dynamic sitemap route. |
package.json:32; package-lock.json:1411 |
Declares and resolves emdash@0.28.1. |
Non-goals
- No Hikaridia production or preview deployment.
- No project-owned sitemap replacement or middleware rewrite.
- No edit to
node_modules and no maintained local patch mechanism.
- No dependency/lockfile upgrade.
- No changes to routing, redirects, content, D1, Workers, Cloudflare, CMS, or
EmDash SEO eligibility behavior.
Proposed upstream issue / PR text
Title: Preserve an explicitly requested trailing slash in collection sitemap URLs
Summary: Collection sitemap URLs currently remove terminal slashes in both
interpolateUrlPattern() and localizePath() normalization. This conflicts
with Astro sites whose canonical and hreflang policy deliberately uses trailing
slashes. Please add the backwards-compatible sitemap.trailingSlash policy
described above, defaulting to the current no-slash behavior, and cover it with
collection- and locale-generic sitemap tests.
EmDash trailing-slash sitemap policy proposal
Status and scope
This is a local, upstream-ready issue/PR draft based solely on the installed
emdash@0.28.1source. It proposes no Hikaridia deployment, project sitemapreplacement,
node_modulespatch, or dependency upgrade.Problem statement
An Astro site may intentionally make its public canonical and hreflang URLs
end in
/. Hikaridia does this throughnormalizeTrailingSlash()insrc/i18n/ui.ts:636and uses it for canonical URLs insrc/layouts/Base.astro:87and locale alternates insrc/layouts/Base.astro:78.EmDash collection sitemaps currently generate the same content URL without a
trailing slash. In installed
emdash@0.28.1:src/astro/routes/sitemap-[collection].xml.ts:98-108resolves every entryfrom its collection
url_pattern, then localizes it before emitting<loc>;src/i18n/resolve.ts:49-65(interpolateUrlPattern) removes a terminal/;src/i18n/resolve.ts:92-98callsnormalizePath; andsrc/i18n/resolve.ts:184-189removes a terminal/again.Consequently, a canonical such as
/en/blog/how-to-verify-lab-grown-diamond-report/can disagree with thecollection sitemap location
/en/blog/how-to-verify-lab-grown-diamond-report.The mismatch affects sitemap
<loc>and sitemap hreflang alternates.Minimal backward-compatible API/design
Add an explicit optional sitemap URL policy to the EmDash Astro integration:
Semantics:
"never"is the default and preserves exactly the current0.28.1no-trailing-slash output.
"preserve"retains a terminal slash deliberately supplied by a collectionurlPattern, including after locale-prefix construction.sitemap hreflang /
x-defaultalternates. It does not change routing,canonical generation, redirects, media URLs, robots, or sitemap-index URLs.
/blog/{slug}/; patterns without a terminal slash stay slashless even under"preserve".This makes output explicit, preserves existing installations by default, and
avoids inferring a site-wide routing policy from Astro internals.
File-level upstream patch plan (not applied)
packages/core/src/astro/integration/runtime.tsand the public EmDashconfig types: add and validate
sitemap.trailingSlash?: "never" | "preserve";default it to
"never".packages/core/src/i18n/resolve.ts: add an explicit resolver option thatpermits preservation of a terminal slash. It must retain the current default
behavior for all existing callers.
packages/core/src/astro/routes/sitemap-[collection].xml.ts: pass thatoption only while resolving sitemap content URLs and all sibling/x-default
alternates. Do not apply it to
buildSeoImageUrl().packages/core/src/astro/routes/sitemap.xml.ts: no functional URL change;retain child sitemap index URLs without trailing slashes.
the
preservemode, and the requirement that a collectionurlPatternitself ends in
/to opt in.Test matrix
Run each case with
trailingSlash: "never"and"preserve". In preservemode, use terminal-slash patterns for the positive cases and a no-slash pattern
for the compatibility case.
posts, published and indexableja,en,zh<loc>equals the intended canonical URL; locale prefix rules are retained; all sitemap hreflang values andx-defaultequal their matching canonical forms.diamonds, published and indexableja,en,zhja,en,zhhas_seo = 1, URL pattern interpolation, and locale output work without a route-specific allowlist.ja,en,zh<image:loc>exactly, emit W3C<lastmod>, retain all sibling alternates andx-default.seo_no_index = 1rows remain absent.neverretain no-trailing-slash collection sitemap URLs; preserve mode does not add/when the pattern has none.Integration assertions:
URLs for each representative route.
sitemap-{collection}.xmlchild URLs.escaping, error responses, cache headers, and locale-unroutable exclusion
remain unchanged.
Installed-source evidence
emdash@0.28.1)src/api/handlers/seo.ts:98-187has_seo = 1collection, retaining only published, non-deleted, indexable rows and returningurl_pattern, locale, translation group, image, and timestamps.src/astro/routes/sitemap-[collection].xml.ts:45-201x-default.src/i18n/resolve.ts:49-65{slug}/{id}and strips terminal slash.src/i18n/resolve.ts:92-98,184-189src/astro/integration/routes.ts:65-72,793-805sitemap.xml, but unconditionally injects the collection dynamic sitemap route.package.json:32;package-lock.json:1411emdash@0.28.1.Non-goals
node_modulesand no maintained local patch mechanism.EmDash SEO eligibility behavior.
Proposed upstream issue / PR text
Title: Preserve an explicitly requested trailing slash in collection sitemap URLs
Summary: Collection sitemap URLs currently remove terminal slashes in both
interpolateUrlPattern()andlocalizePath()normalization. This conflictswith Astro sites whose canonical and hreflang policy deliberately uses trailing
slashes. Please add the backwards-compatible
sitemap.trailingSlashpolicydescribed above, defaulting to the current no-slash behavior, and cover it with
collection- and locale-generic sitemap tests.