main
md 72 lines 3.27 KB
Rendered Raw
1 # Social link unfurls & SEO for public repos
2
3 How pasted siGit links become preview cards + backlinks, and how to verify it.
4
5 ## What's emitted
6
7 Every page renders, in the **initial server HTML** (no JS required):
8
9 - `<title>`, `<meta name="description">`, `<link rel="canonical">`
10 - Open Graph: `og:type`, `og:site_name`, `og:title`, `og:description`, `og:url`,
11 `og:image` (+ `og:image:type/width/height/alt`), `og:locale`
12 - Twitter: `twitter:card=summary_large_image`, title, description, image, alt
13 - JSON-LD: `Organization` + `WebSite` sitewide; `SoftwareSourceCode` on public
14 repo pages.
15
16 Public repo pages (`repositories#show`, `#tree`, `#cicd`, `blobs#show`,
17 `repositories#model`) override the defaults with the repo's name, owner, and
18 description via `app/views/shared/_repository_social.html.erb` + `SeoHelper`.
19
20 Auth / transactional pages (`/auth*`, `/settings`, `/billing`, `/new`) call
21 `noindex!` in their controller, emitting `robots: noindex, nofollow`.
22
23 ## OG share-card images
24
25 `GET /og/:owner/:repo.png` → a 1200×630 PNG (repo name, owner, wrapped
26 description, primary-language dot, star count, siGit wordmark). `GET /og.png` is
27 the generic sitewide card and the default `og:image`.
28
29 - Rendered by `OgImageService` (SVG template → PNG via **libvips**; the prod
30 image installs `libvips`, `librsvg2-2`, and `fonts-dejavu-core`).
31 - Cached by content hash (`Repository#og_version`: tip commit + name +
32 description + stars). Served `public`, long max-age, with an `ETag` so repeat
33 fetches are `304`s. Never generated on the hot path when cached.
34 - **Privacy:** private repos `404` here (no card, even for the owner) and are
35 excluded from the sitemap. A logged-out crawler hitting a private repo URL
36 gets the static `404` with zero metadata.
37 - **Fallback:** if rasterization is unavailable, the endpoint serves the static
38 brand icon with a short TTL so it self-heals — it never 500s.
39
40 ## robots.txt / sitemap.xml
41
42 - `public/robots.txt` allows content, disallows `/auth /settings /billing /new
43 /api/ /*/raw/`, and points at the sitemap.
44 - `GET /sitemap.xml` (`SitemapsController`) lists marketing/legal pages, every
45 public repo, and the profiles that own one. Private repos and
46 private-only users are excluded. Cached 6h.
47
48 ## Verify locally
49
50 ```bash
51 # Meta tags are in the initial HTML (simulate a crawler):
52 curl -s -A "Twitterbot" http://localhost:3000/<owner>/<repo> | grep -iE 'og:|twitter:|canonical|application/ld'
53
54 # Private repo leaks nothing and 404s:
55 curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/<owner>/<private-repo> # 404
56
57 # OG image (needs libvips locally; otherwise returns the static fallback PNG):
58 curl -sI http://localhost:3000/og/<owner>/<repo>.png # 200, Content-Type: image/png, ETag, Cache-Control: public
59 curl -s http://localhost:3000/robots.txt
60 curl -s http://localhost:3000/sitemap.xml | head
61
62 # Tests:
63 bin/rails test
64 ```
65
66 External validators (run against a deployed public URL): X Card Validator,
67 Facebook Sharing Debugger, LinkedIn Post Inspector, Google Rich Results Test.
68
69 > Note: macOS dev hosts usually lack libvips, so the OG endpoint returns the
70 > static fallback locally — the card itself renders in CI/production. To preview
71 > the card design without libvips, dump the SVG
72 > (`OgImageService.send(:repository_svg, repo)`) and open it in a browser.