fix(seo): give every page its own title, canonical and sitemap entry - #150
Merged
Conversation
Searching for an article by name found nothing, and the sitemap advertised ten URLs. Three separate causes, all in the Worker. The `<title>` was never injected. `buildMetaTags` produced og:title and twitter:title but left the shell's tag alone, and a second `<title>` does not override the first — so every article, post and profile on the site was titled "TDN - The Developer Network". Sharing a link looked correct the whole time, because the OG tags were right; only search was affected, which is why it went unnoticed. `injectIntoHead` now strips the shell's tag, and an article title is branded `… · TDN` unless it already carries the name. No `<link rel="canonical">` reached crawlers either. Every branch already computes the canonical URL for og:url; it is now emitted as a link too. The sitemap asked `/posts` for `limit=100`. Both list endpoints cap it at 50 and answer 400, which `fetchPostPage` swallows into an empty page, so the live sitemap carried no posts and — profiles being derived from post authors — no profiles, while still returning 200. Six pages of 50 keep the previous reach. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MyKUa9kaZDJbC7D1j7w2Fs
Contributor
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
tdn-client | c639fd7 | Commit Preview URL Branch Preview URL |
Aug 30 2026, 06:09 AM |
11 tasks
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.
Summary
Searching for one of our own articles by name found nothing, and
/sitemap.xmladvertised ten URLs. Three separate causes, all in the Worker.1. The
<title>was never injected.buildMetaTagsproducedog:titleandtwitter:titlebut left the shell's own tag alone — and a second<title>does not override the first, so every article, post and profile on the site was served as:Google titles a result from
<title>, so no page was findable by its own name. Sharing a link looked perfect the whole time because the OG tags were correct, which is exactly why this went unnoticed.injectIntoHeadnow strips the shell's tag before appending, and an article title is branded… · TDNunless it already carries the name (posts read "… on TDN", profiles "… (@handle) - TDN", so they are left alone).2. No
<link rel="canonical">reached crawlers. TheSEOcomponent sets one client-side, but the crawler-visible HTML had none. Every branch already computes the canonical URL forog:url; it is now emitted as a<link>too.3. The sitemap carried no posts and no profiles. It asked
/postsforlimit=100:Both list endpoints cap
limitat 50, and 100 is a rejection rather than a truncation.fetchPostPageswallows a failed page into[], so all three pages came back empty and the sitemap shipped with zero posts and — profiles being derived from post authors — zero profiles, while still returning 200 and looking healthy. Six pages of 50 keep the previous reach of 300.Verified against production before and after
Type of change
How was this tested?
pnpm exec tsc -bpnpm lintpnpm testpnpm test:e2e(full suite)New coverage:
worker/index.test.ts— exactly one<title>and one canonical per page, the branding rule in both directions, and a sitemap test whose/postshandler enforces the 50 cap the real API enforces. The old handler ignored the query string entirely, which is why nothing caught this.e2e/worker/worker.spec.ts— the title and canonical asserted against the real built shell underwrangler dev, where the tag is indented inside<head>. A fixture cannot prove that.Screenshots (if applicable)
N/A — nothing visual changes; this is all in the crawler-visible head.
Checklist
pnpm lintpassespnpm exec tsc -bpasses (no type errors)anytypes introducedconsole.logleft in codeLeft alone deliberately
<html lang="en">on Turkish articles. A wrong language signal, but fixing it properly means the API exposing an article language; hard-coding per-route would be a guess.x-robots-tag: noindexon/sitemap.xmlis ours and is correct — it keeps the sitemap itself out of the index without affecting how it is read.<div id="root"></div>. Google renders JS, so pages do get indexed, just more slowly than server-rendered content would. Changing that is an SSR decision, not a bug fix.🤖 Generated with Claude Code
https://claude.ai/code/session_01MyKUa9kaZDJbC7D1j7w2Fs