Repository navigation
Conversation
Vondry
force-pushed
the
fix/singleton-canonical-slug
branch
from
June 18, 2026 08:45
ed159a1 to
f966cb9
Compare
Vondry
force-pushed
the
fix/singleton-canonical-slug
branch
from
August 17, 2026 11:22
f966cb9 to
26334a0
Compare
…ing query params
Singletons are served at their slugless listing URL (ListingController
forwards a singleton listing to the record), but getCanonicalRouteAndParams
returned the record-detail route, exposing two URLs for identical content
(/{singularSlug} and /{singularSlug}/{slug}). Canonicalize singletons to the
slugless /{singularSlug} URL instead.
Also stop merging listing query params (order/status/filters) into the
listing canonical, where they appeared as ?order=-createdAt&status=published.
… canonical Unit tests for ContentHelper canonical route resolution: - singleton -> slugless listing route (no slugOrId) - non-singleton -> record route with slug (regression guard) - homepage singleton -> homepage route, not listing (precedence) - non-Content input is ignored Functional tests: - listing canonical omits volatile query params (order/status/filters) - a singleton's slugged record URL canonicalizes to its slugless URL, and the slugless URL is self-referential (end-to-end) Support changes: - add an 'about' non-homepage singleton to the test-scaffolding content types so the end-to-end singleton test has a record to exercise - ContentTypesParserTest: demo content-type count 9 -> 10, assert 'about' key - phpunit.xml.dist: set BOLT_CANONICAL so the Canonical service bootstraps in the test environment (otherwise it fatals on an empty value)
Vondry
force-pushed
the
fix/singleton-canonical-slug
branch
from
September 29, 2026 11:45
26334a0 to
0a94547
Compare
Vondry
added a commit
to Vondry/bolt-skills
that referenced
this pull request
Oct 26, 2026
… is released Up to bolt/core 6.1.10, Bolt's canonical names a singleton by its record route (/kontakt/kontakt-2 instead of /kontakt) and keeps the listing query (/clanky?order=-createdAt&status=published). bolt-seo 2.3 builds og:url and twitter:url from that canonical. Add section 1.6 and three theme partials (examples/_canonical.macros.twig, _canonical.twig, _seo_metatags.twig). They correct the canonical the way bolt/core#3735 does, and both <link rel="canonical"> (Bolt's canonical_template) and bolt-seo's meta tags (templates.meta) print it, so the three tags always name the same URL. The section says when to remove the override: once the project runs a bolt/core release with #3735. The partials are byte-identical to the five sites that use them. Also: - appolodev/bolt-seo ^2.3. The example config and section 1.3 use default.robots instead of the dead meta_robots. - Section 1.5: pages that include @seo/_metatags.html.twig themselves pass canonical: seo.canonical(), not app.request.uri (which keeps the query). - verify.sh step 7 checks the override, and step 2 warns about the dead meta_robots / allow keys. WORKFLOWS 3.1, 6.2 and Appendix B.10, the bolt-audit checklist, AGENTS.md and README follow.
Vondry
added a commit
to Vondry/bolt-sitemap
that referenced
this pull request
Oct 27, 2026
/sitemap.xml is a sitemap index with one child sitemap per segment: the homepage, singletons and static routes (/sitemap-main.xml), each ContentType (/sitemap-<slug>.xml, split at chunk_size URLs) and the configured taxonomies. It follows sitemap-xml-specification-v3.md: - one <url> per page and locale, with the complete, reciprocal set of hreflang alternates and x-default; - singletons at their slugless page, on the listing route with their singular slug, as bolt/core#3735 makes their canonical URL; a custom record_route is kept as is (from bolt/sitemap-extension#5); - TemplateController routes with their {_locale} twins as alternates (from bolt/sitemap-extension#4), without the template file time as <lastmod>, which is the time of the last deploy; - listing pages only while they show published records, with alternates on the listing route; - <lastmod> from the record in UTC; for listing pages and child sitemaps also the last time a URL was added or removed, kept in var/data/tomvondracek-sitemap.json; none where no date is known; - no changefreq, priority, image:title or XSLT; image and xhtml namespaces only where used; absolute, percent-encoded URLs on the canonical host; - application/xml, X-Robots-Tag: noindex and a content-hash ETag; the XML is cached until content changes. With appolodev/bolt-seo installed, pages it marks noindex or gives another canonical URL are left out. Only its config and field values are read, never its classes, so it stays optional; a contract test checks the result against bolt-seo's own Seo class. The XML is rendered from Twig templates a theme can override, and SitemapUrlsEvent lets a project add or remove URLs. An opt-in stylesheet and script show the sitemap to people in a browser without XSLT.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A singleton content type ends up with two indexable URLs serving identical content, and the on-page canonical points at the "ugly" one:
ListingControllerforwards a singleton "listing" to the record), e.g./about.ContentHelper::getCanonicalRouteAndParams()returns the record-detail route, so the<link rel="canonical">becomes/about/{slug}— where{slug}is an auto-generated record slug (e.g.about-2) that carries no meaning for a one-record type.Separately, listing canonicals leak volatile query params:
ListingControllermerges the parsed query params (order,status, filters) into the canonical, producing URLs like/showcases?order=-createdAt&status=published.Fix
/{singularSlug}) instead of the record-detail route. The homepage (also a singleton) keeps resolving to the homepage route — its early-return precedence is preserved.Because
getLink()(record|link) andsetCanonicalPath()sharegetCanonicalRouteAndParams(), internal links and the canonical tag now agree for singletons.Changes
src/Utils/ContentHelper.php— add a singleton branch togetCanonicalRouteAndParams().src/Controller/Frontend/ListingController.php— stop merging query params into the listing canonical.Tests
tests/php/Utils/ContentHelperTest.php): singleton → slugless listing route (noslugOrId); non-singleton → record route + slug (regression guard); homepage singleton → homepage route,not listing; non-
Contentinput ignored.tests/php/Controller/Frontend/ListingCanonicalTest.php): a listing canonical omits query params.tests/php/Controller/Frontend/SingletonCanonicalTest.php): a singleton's slugged record URL canonicalizes to its slugless URL, and the slugless URL is self-referential.Each test was mutation-verified — it fails when its corresponding fix is reverted.
Supporting test changes
config/bolt/contenttypes.yaml— add anaboutnon-homepage singleton to the test-scaffolding content types so the end-to-end test has a record to exercise (the only existing singleton,homepage, is special-cased).tests/php/Configuration/Parser/ContentTypesParserTest.php— demo content-type count 9 → 10, assert theaboutkey.phpunit.xml.dist— setBOLT_CANONICALso theCanonicalservice bootstraps in the test environment (it otherwise fatals on the empty default value).Test screenshots
1. Homepage singleton test case (This already works -> nothing changes)
2. Contact page singleton test cases (This did not work -> now works as homepage singleton)
Notes
RelationFactoryTestis occasionally flaky due to unseeded Faker fixtures; unrelated to this change.