Social link unfurls & SEO for public repos
How pasted siGit links become preview cards + backlinks, and how to verify it.
What's emitted
Every page renders, in the initial server HTML (no JS required):
<title>,<meta name="description">,<link rel="canonical">- Open Graph:
og:type,og:site_name,og:title,og:description,og:url,og:image(+og:image:type/width/height/alt),og:locale - Twitter:
twitter:card=summary_large_image, title, description, image, alt - JSON-LD:
Organization+WebSitesitewide;SoftwareSourceCodeon public repo pages.
Public repo pages (repositories#show, #tree, #cicd, blobs#show,
repositories#model) override the defaults with the repo's name, owner, and
description via app/views/shared/_repository_social.html.erb + SeoHelper.
Auth / transactional pages (/auth*, /settings, /billing, /new) call
noindex! in their controller, emitting robots: noindex, nofollow.
OG share-card images
GET /og/:owner/:repo.png → a 1200×630 PNG (repo name, owner, wrapped
description, primary-language dot, star count, siGit wordmark). GET /og.png is
the generic sitewide card and the default og:image.
- Rendered by
OgImageService(SVG template → PNG via libvips; the prod image installslibvips,librsvg2-2, andfonts-dejavu-core). - Cached by content hash (
Repository#og_version: tip commit + name + description + stars). Servedpublic, long max-age, with anETagso repeat fetches are304s. Never generated on the hot path when cached. - Privacy: private repos
404here (no card, even for the owner) and are excluded from the sitemap. A logged-out crawler hitting a private repo URL gets the static404with zero metadata. - Fallback: if rasterization is unavailable, the endpoint serves the static brand icon with a short TTL so it self-heals — it never 500s.
robots.txt / sitemap.xml
public/robots.txtallows content, disallows/auth /settings /billing /new /api/ /*/raw/, and points at the sitemap.GET /sitemap.xml(SitemapsController) lists marketing/legal pages, every public repo, and the profiles that own one. Private repos and private-only users are excluded. Cached 6h.
Verify locally
# Meta tags are in the initial HTML (simulate a crawler):
curl -s -A "Twitterbot" http://localhost:3000/<owner>/<repo> | grep -iE 'og:|twitter:|canonical|application/ld'
# Private repo leaks nothing and 404s:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/<owner>/<private-repo> # 404
# OG image (needs libvips locally; otherwise returns the static fallback PNG):
curl -sI http://localhost:3000/og/<owner>/<repo>.png # 200, Content-Type: image/png, ETag, Cache-Control: public
curl -s http://localhost:3000/robots.txt
curl -s http://localhost:3000/sitemap.xml | head
# Tests:
bin/rails test
External validators (run against a deployed public URL): X Card Validator, Facebook Sharing Debugger, LinkedIn Post Inspector, Google Rich Results Test.
Note: macOS dev hosts usually lack libvips, so the OG endpoint returns the static fallback locally — the card itself renders in CI/production. To preview the card design without libvips, dump the SVG (
OgImageService.send(:repository_svg, repo)) and open it in a browser.