Laravel 13, Blade, two locales, no SEO package.
A bilingual public site usually carries four lists of the same URLs, and each one is an SEO surface. The routes file holds one. The sitemap builder holds a second. The layout that prints canonical and hreflang tags holds a third. The language switcher in the footer holds a fourth.
Nothing makes those four agree, and they come apart in a specific way. Someone adds a page, the route lands, and the other three get updated by whoever remembers. When they do not, nothing happens. No exception, no failing test, and the page answers 200 to every visitor who has its address. The consequence surfaces weeks later in Search Console, as a page nobody ever crawled or a hreflang tag pointing at a 404. That delay is what makes URL drift an SEO problem rather than a bug: by the time it is visible, the commit that caused it is months old.
We built our public site so those four lists cannot exist. There is one table, keyed by page, and everything a crawler sees is a projection of it. The site itself runs inside the same Laravel application as the product, in Blade, which is what makes a single table plausible in the first place.
31 page keys, two locales, 136 sitemap URLs on the day this was written. One expression decides every SEO surface of the site: the routes, the canonicals, the hreflang pairs, the x-default, the sitemap, the Open Graph card, the language switcher and the navigation. Fifty files in the application read it, and forty nine test files hold it in place.






