Repository navigation
feat(seo): use branded og images per section - #480
Merged
Merged
Conversation
Section pages share one fixed 1200x630 card per locale, rendered from the ejected NuxtSeo.satori template by scripts/render-og-images.mjs and uploaded to images/og/<section>-<locale>.png. Home, blog, projects, community, community-poi, events, team, about and bluemap pass their card to usePageSeo; project detail pages without a logo fall back to the projects card. Claude-Session: https://claude.ai/code/session_019zws4ceJu2jctk3XT48fMX
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
launchpad | 693a3fa | Commit Preview URL Branch Preview URL |
Oct 10 2026, 10:54 PM |
Section og images live behind the image proxy, so the local check now decides by whether the file exists in public/ instead of a fixed list of routes. Claude-Session: https://claude.ai/code/session_019zws4ceJu2jctk3XT48fMX
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.
Summary
Section pages (home, blog, projects, community, community POI, events, team, about, BlueMap) now share a branded 1200x630 social card per locale instead of falling back to
/images/og-default.png. Project detail pages without a logo use the projects card.The cards are rendered once by
scripts/render-og-images.mjsfrom the realcomponents/OgImage/NuxtSeo.satori.vuetemplate (no prerender pass) and uploaded to the image bucket. The template itself is rebranded: OneLiteFeather logo (240 px, about 2x the previous size), "OneLiteFeather.net" wordmark, MD3 dark-scheme colours, title and description props only.Problem
With
ogImage.zeroRuntime: trueand nonitro.prerender, no OG images are generated. Every page without its own image sharesog-default.png, and the ejected template still showed the nuxt-og-image "Nuxt SEO" logo.Decision: fixed images per section, no prerender
Prerendering was tried first (recipe in
references/og-images.md,crawlLinksfrom/enand/de) and rejected:.output/public. The generatedwrangler.jsonhas norun_worker_first, so Workers Assets would serve those pages ahead of the Worker. That bypasses SSR, the time-dependent event phase, and the A/B flag.defineOgImagereturns[]outside prerender (zero-runtime.js), so only the prerendered HTML carried the generatedog:image. Deriving the URL at runtime would mean replicating the module's/_og/s/<encoded params>scheme.@resvg/resvg-jsbinding is not installed (fixed with the already-installed WASM build, but only relevant if prerender is used).TeamMembercards fail because the module blocks the remotemc-heads.netavatar. Both are moot for this approach.This PR does not add
nitro.prerender, does not touchzeroRuntime, and does not add prerendered or static HTML.Uploaded keys
All 18 keys were uploaded with
rclone copyto --s3-no-check-bucket --immutable, and only after every upload had finished were they requested. Each was checked withcurl -sI https://img.onelitefeather.net/images/og/<key>:images/og/home-de.png,images/og/home-en.pngimages/og/blog-de.png,images/og/blog-en.pngimages/og/projects-de.png,images/og/projects-en.pngimages/og/community-de.png,images/og/community-en.pngimages/og/community-poi-de.png,images/og/community-poi-en.pngimages/og/events-de.png,images/og/events-en.pngimages/og/team-de.png,images/og/team-en.pngimages/og/about-de.png,images/og/about-en.pngimages/og/bluemap-de.png,images/og/bluemap-en.pngRe-rendering reproduces the uploaded bytes: the MD5 of a fresh
home-de.pngandhome-en.pngequals the bucket ETags.Wiring
image: sectionOgImage('<section>', locale.value)tousePageSeo, or touseHomeSeoon the home page.sectionOgImagelives inlayers/content-core/utils/sectionOgImage.ts.img()transform as other page images (fit: cover, 1200x630). A quick check of…/cdn-cgi/image/w=1200,h=630,fit=cover,f=webp,q=80/images/og/home-de.pngreturned a 1200x630 PNG, so there is no recrop.image: project.value?.logo || sectionOgImage('projects', locale.value).og-default.png.Regenerating the cards
Re-run after changing the template or a section's i18n title or description. Its
SECTIONSlist must matchOG_SECTIONSinsectionOgImage.ts;tests/seo/section-og-images.spec.tsenforces this.Rendered card
The card is 1200x630 on the dark MD3 surface (
#121319). The feather logo is on the left at 240 px, with the "OneLiteFeather.net" wordmark in the primary colour above the page title (68 px, up to two lines). The description sits below in the on-surface-variant colour, and a gradient bar in the primary, secondary and tertiary colours runs along the bottom.Verification
pnpm test: 166 files, 1313 tests pass. New specs:tests/seo/og-image-template.spec.ts(6) andtests/seo/section-og-images.spec.ts(16).pnpm quality: ESLint errors 186, warnings 17, TypeScript errors 3, all at baseline.pnpm build: passes..output/public/_ogis absent, and the only prerendered routes are the existing content SQL dumps.NUXT_IMAGE_PROVIDER=cloudflare): not a usable production check. In dev the module'sdefineOgImageis active and overridesog:imagewith/_og/d/...URLs. The production value comes fromusePageSeo'ssocialImage, which the spec and build cover.Not in this PR
TeamMember.satori.vueis unchanged. Its remote avatar fetch is blocked at render time, so it is out of scope.defineOgImage('NuxtSeo.satori', …)call, which is a no-op at runtime under zeroRuntime and was left as is.https://claude.ai/code/session_019zws4ceJu2jctk3XT48fMX