fix(build): sitemap emits served route for index pages, not /index - #12
Merged
Conversation
Root index.mdx renders at `/` but slugifies to the bare string `index` (scan-pages' collapse regex needs a leading `/`, so only nested foo/index.mdx collapses). The sitemap/llms.txt emitters derived URLs straight from the slug, publishing `/index` — which 307s to `/`. Google classifies redirecting sitemap URLs as "Page with redirect" and drops them. Add pageRouteForSlug/pagePathForSlug to @tanglydocs/schema (the shared leaf, same rationale as resolve-site) and route sitemap, llms.txt, llms-full.txt, agent .md preambles and Seo.astro's canonical through it, so published URLs can't diverge from the canonical again. og card filenames stay slug-keyed (dist/og/<slug>.png, home => index.png) — deliberately the inverse mapping, kept separate.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
tangly-docs | 21b0e3c | Commit Preview URL Branch Preview URL |
Jul 17 2026, 06:58 AM |
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.
The defect
Generated
sitemap.xmlpublishes/indexfor a rootindex.mdx. That URL 307s to/, so Google classifies it as "Page with redirect" and excludes it: wasted crawl budget plus a duplicate signal against the real home page.Redirects and canonicals were already correct. The defect was isolated to the build-time URL emitters, which derived URLs from the source file path instead of the served route.
Live evidence (docs.squirrelscan.com, a Tangly-built site)
sitemap.xml<loc>https://docs.squirrelscan.com/index</loc>GET /index/GET /<link rel="canonical" href="https://docs.squirrelscan.com/">Root cause
scanPagesderives slugs from the file path and collapsesindexwith.replace(/\/index$/, ""). That regex needs a leading slash, so nestedcli/index.mdxcorrectly collapses tocli, but a rootindex.mdxslugifies to the bare stringindex. It renders at/and is only also routable at/index.Seo.astroalready special-cased this for the canonical tag. The sitemap/llms emitters did not, and there was no shared helper keeping the two in sync.The fix
Add
pageRouteForSlug/pagePathForSlugto@tanglydocs/schema— the shared leaf bothtanglyand the themes depend on, exposed via a./page-pathsubpath (same rationale asresolve-site: themes must not importtangly, and the subpath keeps zod out of theme bundles).Route every published URL through it, so the sitemap can't drift from the canonical again:
generateSitemapgenerateLlmsTxt/generateLlmsFullTxt— same bug, also linked/indexwritePageMarkdown'sURL:preamble — same bugSeo.astro's canonical — replaces its inlined special caseThe helper also collapses a trailing
/indexitself. That duplicates whatscanPagesalready does to nested slugs, but means any caller holding a raw file-derived slug resolves the same route rather than depending on an upstream regex.Not changed: og card filenames stay slug-keyed (
dist/og/<slug>.png, home →index.png). That's a filename, deliberately the inverse mapping of a route, and routing it through the helper would have pointedog:imageat a non-existent file. Verified/og/index.pngstill emits and resolves.Trailing-slash decision
No trailing slash, matching the existing
Seo.astrobehavior exactly (.replace(/\/+$/, "") || "/") rather than inventing a convention. Root →/; under--base /docs, home →/docs(not/docs/). The emitted sitemap entry is now byte-identical to the canonical tag.Verification
Built a fixture with both a root
index.mdxand a subdirectorycli/index.mdx, at the pre-fix and post-fix commits:Post-fix canonical on
/ishttps://docs.example.com/— an exact match with the sitemap entry.Note on the subdirectory case
Worth recording: subdirectory
index.mdxwas never actually broken in the sitemap.scanPagesalready collapsescli/index→cli, and the pre-fix build confirms it emitted/clicorrectly. TheGET /cli/index→ 307 you can observe live is just Astro also routing that path; the sitemap never contained it. Only the root case was ever wrong. Tests cover both regardless, to pin the behavior.Tests
packages/schema/src/page-path.test.ts— root, nested, base-prefixed, stray slashes, andindexappearing inside a segment name (/indexingmust not be stripped).packages/tangly/src/build-outputs/index.test.ts— fixture gains rootindex.mdx+ subdirectorycli/index.mdx; asserts sitemap and llms.txt emit served routes, with and without a base prefix.bun run cigreen (format, lint, build, typecheck, tests — 22 lint warnings all pre-existing, 0 errors).