Skip to content

feat: add per-page Open Graph images#196

Open
rileychh wants to merge 2 commits into
mainfrom
og-images
Open

feat: add per-page Open Graph images#196
rileychh wants to merge 2 commits into
mainfrom
og-images

Conversation

@rileychh

@rileychh rileychh commented Jun 9, 2026

Copy link
Copy Markdown
Member

變更內容

Closes #127.
在建置階段為每個路由產生專屬的 Open Graph 預覽圖。

  • 使用 nuxt-og-image(Takumi renderer)在 nuxt generate 時輸出 PNG。
  • 議程頁:卡片含標題、講者、議程室與時間。
  • 其他頁面:使用 Default 樣板。
  • 議程圖片 zh / en 雙語皆預先產生。

字型與 CJK

  • 透過 @nuxt/fonts 載入 Noto Sans SC / TC / JP / KR (皆 weight 400)。
  • SC 為主要字型:Takumi 每個 script run 只選一種字型,SC 漢字涵蓋最廣 (簡繁皆以正確字形呈現)。
  • weight 僅 400(700 字重的 CJK 會被 Takumi 渲染污損)。
  • 加入 @iconify-json/noto 完整支援 emoji(模組預設只內建約 100 個)。

建置

  • build script 以 cross-env 將 heap 提高到 8 GB (4 套 CJK 字型 + 約 670 張圖片)。
  • nitro.prerender.failOnError: false 容忍 Pretalx API 偶發 500。
  • 移除全站固定的 og:image / twitter:image,改由模組逐頁注入。

建置過程會出現 Could not resolve font "Segoe UI" for OG images. 等找不到字體的錯誤,這是因為 UnoCSS preset-wind4 包含這些字體,但 nuxt-og-imageisSystemFont() 誤以為他們是外部字體而不是系統 fallback。這類警告可以安全的忽略。

樣式為何止於 inline style + 常數 (無法再進一步重構)

兩個樣板共用的色彩、字型、Logo path 與基礎版面已抽到 app/components/OgImage/theme.ts,以 inline :style 套用。Takumi renderer 的限制讓 CSS class 或共用 CSS 檔無法運作。實測結果:

做法 結果
inline :style + theme.ts 常數 正常
主題色 class(bg-primary-800) renderer timeout
共用檔 <style src="./og.css"> 被忽略
<script setup>import './og.css' 被忽略

原因:unocss-preset-theme 以 CSS 變數(var(--primary-800))定義主題色,但靜態 renderer 取不到變數定義而卡住逾時;且 Takumi 只讀 inline :style、inline <style> 文字與編譯後的 utility class,從不讀取外部或 JS 匯入的 CSS 檔。因此唯一同時「能共用又能正確渲染」的方式就是 JS/TS 常數。

驗證

  • 已讀取產出的 PNG 確認簡體、繁體、日文、emoji 與英文字降部皆正常,Logo 為 cp-green。
  • 請 reviewer 確認這樣的臨時版面在主視覺出來之前沒有問題

一些範例:

中文文章 英文文章
c_Default,title_~5Lqk6YCa,subtitle_~5YmN5b6AIENPU0NVUCAyMDI2IHggVWJ1Q29uIEFzaWEg5rS75YuV5Zyw6bue4oCU4oCU5ZyL56uL6Ie654Gj56eR5oqA5aSn5a2455qE5Lqk6YCa5pa55byP44CC,p_Ii90cmFuc3BvcnRhdGlvbiI c_Default,title_Transportation,subtitle_How+to+reach+the+COSCUP+2026+x+UbuCon+Asia+venue+at+National+Taiwan+University+of+Science+and+Technology ,p_Ii9lbi90cmFuc3BvcnRhdGlvbiI
預設圖片 含 Emoji 的議程
c_Default c_Session,title_~4pqhTGlnaHRuaW5nIHRhbGvimqEgWCAgQ2xvc2luZyBEYXkgMg,speakers_COSCUP+Team,room_RB105,time_~MTY6MTUgfiAxNzowMA,p_Ii9lbi9zZXNzaW9uL1ZCTFhZOSI

@rileychh rileychh requested review from a team and Copilot June 9, 2026 10:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds build-time, per-route Open Graph image generation for the Nuxt site (including session pages with agenda-specific cards), replacing the previous site-wide static og:image/twitter:image approach and ensuring images are produced during nuxt generate.

Changes:

  • Integrates nuxt-og-image (Takumi renderer) and adds OG templates (Default and Session) plus per-page defineOgImage(...) usage.
  • Extends prerendering to include bilingual session detail routes (/session/:id and /en/session/:id) and relaxes Nitro prerender error handling.
  • Adds build/runtime dependencies and build script changes to support large-scale OG image generation (fonts, renderer deps, increased Node heap).

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks newly added OG-image, renderer, font, and build tooling dependencies.
package.json Adds nuxt-og-image + renderer deps; increases build heap via cross-env in build script.
nuxt.config.ts Registers modules, configures fonts for OG rendering, disables Nitro prerender fail-on-error, removes global static og/twitter image meta.
app/app.vue Adds site-wide fallback defineOgImage('Default').
app/pages/session.vue Prerenders both zh (default) and /en session detail routes.
app/pages/session/[id].vue Defines per-session OG images using the Session template with fetched session metadata.
app/pages/[...slug].vue Defines per-content-page OG images using the Default template populated from content metadata.
app/components/OgImage/theme.ts Introduces shared inline-style constants for OG image templates.
app/components/OgImage/Default.takumi.vue Implements default OG card template layout for non-session pages.
app/components/OgImage/Session.takumi.vue Implements session OG card template layout (title/speakers/room/time).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/components/OgImage/Session.takumi.vue
Comment thread app/components/OgImage/Default.takumi.vue
@rileychh rileychh linked an issue Jun 9, 2026 that may be closed by this pull request
Base automatically changed from page-descriptions to main June 14, 2026 06:20
@rileychh-dokploy-coscup

rileychh-dokploy-coscup Bot commented Jun 15, 2026

Copy link
Copy Markdown

Dokploy Preview Deployment

Name Status Preview Updated (UTC)
Nuxt ✅ Done Preview URL 2026-06-15T08:35:09.879Z

rileychh added 2 commits June 16, 2026 17:23
Generate a branded OG image per route at build time with nuxt-og-image
(Takumi renderer). Sessions render a card with title, speakers, room, and
time; every other page uses a Default template. Session images are
prerendered for both zh and en.

- Add @nuxt/fonts with Noto Sans SC/TC/JP/KR (weight 400) for CJK glyph
  coverage. SC is primary: Takumi selects one font per script run rather
  than per glyph, and SC has the widest Han coverage (Simplified and
  Traditional, each in its correct form).
- Add @iconify-json/noto for full emoji coverage (the module bundles only
  ~100 by default).
- Raise the build heap to 8 GB via cross-env for 4 CJK fonts + ~670 images.
- Set nitro.prerender.failOnError: false to tolerate transient Pretalx 500s.
- Drop the static site-wide og:image/twitter:image meta; the module now
  injects both per page.
- Share the colors, font-family chain, logo path, and base card layout
  across both templates via a small app/components/OgImage/theme.ts module.
Unbounded parallel prerendering spiked to ~12.6GB RSS during the
session OG card render burst, OOM-killing the 16GB CI runner
non-deterministically. Capping concurrency to 4 bounds the peak to
~10.2GB (independent of session count) while keeping build time flat.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4003c3cb20

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread nuxt.config.ts
publicDir: process.env.NUXT_OUTPUT_DIR || '.output/public',
},
prerender: {
failOnError: false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep prerender failures blocking deploys

This site is uploaded as a static GitHub Pages artifact, so there is no Nitro runtime to recover if a page or one of the new generated /_og/s/... image routes fails during pnpm build. With failOnError: false, the deploy workflow can still publish .output/public after an OG render/API prerender error, leaving missing social images or routes in production instead of failing the build; prefer keeping this strict and handling flaky Pretalx calls with retries or targeted fallbacks.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果不使用 failOnError: true 的話,一個 Pretalx 錯誤或是一個 OG Image 失敗渲染就會讓整個網站無法部署。現在失敗的 OG Image 會自動回退到預設圖片。大家認為應該要優先部屬穩定還是優先內容正確?

Comment thread app/components/OgImage/theme.ts
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.

og image 支援

3 participants