Skip to content

fix: serve OG image as static public/og-image.png for LinkedIn#55

Merged
rz1989s merged 1 commit into
mainfrom
fix/og-image-static-png
May 26, 2026
Merged

fix: serve OG image as static public/og-image.png for LinkedIn#55
rz1989s merged 1 commit into
mainfrom
fix/og-image-static-png

Conversation

@rz1989s
Copy link
Copy Markdown
Member

@rz1989s rz1989s commented May 26, 2026

Summary

  • PR fix: update OgImageGenerator to MiniMagick v5 API #54 fixed the MiniMagick v5 crash, so /og-image started returning a real PNG. But LinkedIn's Post Inspector still reported No image found for https://rectorspace.com.
  • Two compounding causes verified against LinkedIn Post Inspector + raw curl:
    1. send_file returns HEAD with content-length: 0 (Rails only writes the body length on GET). LinkedIn HEADs first and treats zero-length as "image missing."
    2. The URL had no file extension. LinkedIn's parser prefers URLs ending in a recognized image extension.
  • Pivot: write the OG image directly to public/og-image.png and let Thruster serve it as a real static asset. Both blockers disappear:
    • Thruster sets a correct Content-Length for HEAD and GET (verified locally: content-length: 51090 on both methods)
    • URL is now /og-image.png
  • Regeneration on boot via a small after_initialize hook in config/initializers/og_image_warmup.rb. Cache check short-circuits when the existing file is newer than achievements.yml, so dev boot isn't slowed down.

Test plan

  • Local bin/rails runner 'OgImageGenerator.cached_path' writes the 51KB PNG to public/og-image.png instead of tmp/cache/og/
  • Local curl -I http://localhost:3334/og-image.png returns 200, content-type: image/png, content-length: 51090
  • Local curl -X GET -I http://localhost:3334/og-image.png returns identical headers
  • bin/rails routes | grep og returns nothing (dynamic route removed)
  • Post-deploy: curl -I https://rectorspace.com/og-image.png matches local
  • LinkedIn Post Inspector re-scrape of https://rectorspace.com shows the rendered OG card under "When this link is shared..."
  • RECTOR LABS LinkedIn Experience link tiles refresh from placeholder thumbs to the brand card

Files

  • app/services/og_image_generator.rb — cache path → public/og-image.png
  • config/initializers/og_image_warmup.rb — regenerate on boot (rescue-guarded)
  • app/views/layouts/application.html.erbog:image + twitter:image/og-image.png
  • config/routes.rb — remove dynamic og-image route
  • app/controllers/og_images_controller.rb — deleted
  • .gitignore — ignore generated public/og-image.png

The previous /og-image controller route was rejected by LinkedIn's Post
Inspector even after the MiniMagick v5 fix:

  Image  No image found
  Alternate values we considered  (empty)

Two compounding causes:

1. Rails' send_file returned HEAD with content-length: 0 (only GET set
   the body length correctly). LinkedIn's image validator HEADs first
   and treats a zero-length response as "image missing."

2. The URL had no file extension. LinkedIn's parser favours URLs that
   end in a recognized image extension when picking the OG image.

Pivoting to a real static asset solves both: Thruster sets a correct
Content-Length on every method, and the URL is now /og-image.png.

The generator now writes directly to public/og-image.png. A small
after_initialize hook regenerates it on every boot so the card always
reflects the latest achievements.yml (achievements only change at
deploy time, which is when the container restarts). Cache check still
short-circuits when the existing file is newer than achievements.yml,
so dev boot stays fast.
@rz1989s rz1989s merged commit e0091d1 into main May 26, 2026
5 checks passed
@rz1989s rz1989s deleted the fix/og-image-static-png branch May 26, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant