Auto-generated social share images for every page and post.
Every page and post gets a proper 1200×630 social share image — site name, title, and your brand color on a clean card — generated automatically. Set a custom image in the SEO fields and it wins; leave it empty and the generated one fills in. Links shared to Slack, LinkedIn or X stop looking like afterthoughts.
An image route composes an SVG template (title text, wrapped and sized to fit) and rasterizes it to PNG with sharp — which Prelo already uses for media resizing, so no new native dependency. Results are cached to disk keyed by content hash, so each image renders once. The seoTags head helper points og:image at the route when no explicit image is set.
You are adding generated OG images to a Prelo project. Requires sharp (already a CMS dependency) and a `SITE_URL`.
1. **Template.** Create `og-template.js` exporting `renderSvg({ siteName, title, accent })` → an SVG string, 1200×630:
- Background: near-white or the project's paper color; a 12px accent bar or corner mark in the project's brand color (read it from existing design tokens/config — do not invent one).
- Site name small at top in the project's font stack (system-safe fallbacks only — SVG rasterization won't load webfonts unless embedded; embed the font as base64 `@font-face` in the SVG only if the project has a local font file).
- Title large (64–88px), wrapped manually: break into lines of ~20 chars at word boundaries, max 4 lines, ellipsize beyond. Escape XML entities.
2. **Route.** Add to `cms.config.js` `routes:` (custom routes mount under `/api/x/`):
```js
"GET /og/:type/:slug": async (req, res, db) => { /* … */ }
```
(Custom routes are Express routes, so `:type`/`:slug` params work — see `prelo docs api`.) The handler must:
- Look up the entry (published only) by type + slug; 404 if missing.
- Compute a cache key `sha256(type + slug + title + updated_at)`; if `data/x-og-cache/{key}.png` exists, stream it.
- Otherwise `sharp(Buffer.from(svg)).png()` → write to cache, then respond `image/png` with `Cache-Control: public, max-age=31536000, immutable`.
- Create the cache dir in `hooks.boot`.
3. **Head wiring.** In the frontend's `seoTags`/head helper: `og:image` = the entry's `seo.ogImage` if set, else `{CMS_URL}/api/x/og/{type}/{slug}`, absolute. Add `og:image:width`/`height` (1200/630) and `twitter:card` = `summary_large_image`.
4. **Homepage.** Give the homepage a generated image too (site name + tagline), same route with a reserved slug or a dedicated `"GET /og-home"` route.
5. **Verify.** Fetch the route for one post — confirm a valid PNG, correct dimensions, second fetch served from cache (log or timing). Paste a URL into a share-preview debugger locally (e.g. `curl -s | head` for the meta tags) to confirm og:image resolves absolutely.Nothing to manage. New content gets an image on first share; editing a title invalidates the cache by key. To override any page's image, the client just sets an OG image in Studio's SEO fields.
Filterable gallery with lightbox, fed straight from the media library.
Contact form with spam protection and email notifications, plus a submissions inbox in Studio.
Signup form with double opt-in, delivering to Resend, Mailchimp or Buttondown.