fix(seo): absolute og:image and single canonical - #77
Closed
pamelachia wants to merge 2 commits into
Closed
Conversation
…iscovery Social unfurls on X, Slack, and LinkedIn were rendering without an image because og:image and twitter:image were emitted as the relative path /img/og-image.png. Every route now uses the absolute defaultOgImage from src/lib/shared.ts, and blog posts resolve a frontmatter image to an absolute URL before it reaches og:image and JSON-LD. The root layout no longer sets canonical or og:url. TanStack Router dedupes meta by property but not links, so the root canonical shipped alongside every route's own canonical, and the root og:url leaked the homepage URL into routes that did not override it. Each page route sets its own og:url and canonical; the root gains the RSS autodiscovery link. The blog index gets og:url, twitter:title/description, a Blog JSON-LD block, and a screen-reader-only h1. Series pages get og:url and their own twitter:title/description. Part of GROWTH-1208 Signed-off-by: Pamela Chia <pamelachiamayyee@gmail.com>
|
@pamelachia is attempting to deploy a commit to the Supabase Team on Vercel. A member of the Team first needs to authorize it. |
The bare /docs page built its canonical as /docs/ from an empty splat, and /docs/ redirects to /docs, so with the root canonical gone the page would have pointed its only canonical at a redirect. absoluteUrl now resolves through the URL parser so rootless, protocol-relative, and mixed-case absolute frontmatter image values all produce a fetchable og:image instead of a concatenated string. The privacy page gets its own og:title and og:description alongside the og:url added earlier. Part of GROWTH-1208 Signed-off-by: Pamela Chia <pamelachiamayyee@gmail.com>
Contributor
Author
|
Superseded by #78 (same branch, pushed to origin now that access is sorted). |
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.
Shared links to multigres.com rendered on X, Slack, and LinkedIn as text-only cards. Every page emitted
og:imageandtwitter:imageas the relative path/img/og-image.png, which unfurlers reject, so the site never showed a preview image despite declaringsummary_large_image. The samehead()functions also carried a duplicate canonical on every page and left the blog index without its own metadata, so I fixed both here too.Changed:
defaultOgImagefromsrc/lib/shared.ts. A blog post's frontmatterimageis resolved through the URL parser (root-relative, rootless, protocol-relative, and absolute values all resolve correctly) before it reaches og:image and JSON-LD.canonicalorog:url. The router dedupesmetaby property but notlinks, so the root canonical shipped next to every route's own, and the root og:url leaked the homepage URL into routes that did not override it. Each page route now sets both. The bare/docspage's canonical drops its trailing slash (/docs/307-redirects to/docs, and the sitemap lists/docs)./bloggets og:url, twitter:title and twitter:description, aBlogJSON-LD block, and a screen-reader-only h1. No markdown alternate link:/bloghas no markdown variant (the middleware insrc/start.tsonly serves.mdfor the homepage, docs pages, and individual posts). Series pages get og:url and their own twitter:title and twitter:description; the privacy page gets its own og:title, og:description, and og:url./blog/rss.xmlwithrel="alternate" type="application/rss+xml".Note: per-page generated OG images are the next step and stay in the Linear issue; I made the existing generic image render first. Series pages still have no JSON-LD, pre-existing and out of scope.
To test
Preview deployments sit behind deployment protection, so unfurlers can only be checked on production after merge.
Tested locally against the built server function (
pnpm build, thenhandler.fetchon.vercel/output/functions/__server.func/index.mjs) over/,/blog,/blog/listen-notify,/blog/series/connection-pooling,/docs,/docs/architecture,/privacy:curl -s <page> | grep -c 'rel="canonical"', expect 1 with href equal to the page URLcurl -s <page> | grep -o '<meta property="og:image"[^>]*>', expect exactly one tag withcontent="https://multigres.com/img/og-image.png"; same fortwitter:image;og:urlequals the canonical;og:titleandtwitter:titleeach appear oncecurl -s /blog, expect an<h1>, oneapplication/ld+jsonblock of typeBlog, andtwitter:titleequal toBlog | Multigres<link rel="alternate" type="application/rss+xml" href="https://multigres.com/blog/rss.xml">Linear