-
Notifications
You must be signed in to change notification settings - Fork 8
feat: add per-page Open Graph images #196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| <script setup lang="ts"> | ||
| import { OG_COLORS, OG_LOGO, ogCardBaseStyle } from './theme' | ||
|
|
||
| withDefaults( | ||
| defineProps<{ | ||
| title?: string | ||
| subtitle?: string | ||
| }>(), | ||
| { | ||
| title: 'COSCUP 2026 x UbuCon Asia', | ||
| subtitle: '', | ||
| }, | ||
| ) | ||
| </script> | ||
|
|
||
| <template> | ||
| <div :style="{ ...ogCardBaseStyle, padding: '80px' }"> | ||
| <div :style="{ display: 'flex', alignItems: 'center' }"> | ||
| <svg | ||
| height="61" | ||
| :viewBox="OG_LOGO.viewBox" | ||
| width="150" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <path | ||
| :d="OG_LOGO.path" | ||
| :fill="OG_COLORS.accent" | ||
| fill-rule="evenodd" | ||
| :transform="OG_LOGO.transform" | ||
| /> | ||
| </svg> | ||
| <span :style="{ marginLeft: '28px', fontSize: '34px', fontWeight: 700, color: OG_COLORS.brand }"> | ||
| COSCUP 2026 x UbuCon Asia | ||
| </span> | ||
| </div> | ||
|
|
||
| <div :style="{ display: 'flex', flexDirection: 'column' }"> | ||
| <div | ||
| :style="{ | ||
| display: 'block', | ||
| fontSize: '60px', | ||
| fontWeight: 400, | ||
| lineHeight: 1.25, | ||
| color: OG_COLORS.text, | ||
| }" | ||
| > | ||
| {{ title }} | ||
| </div> | ||
| <div | ||
| v-if="subtitle" | ||
| :style="{ | ||
| display: 'block', | ||
| marginTop: '28px', | ||
| fontSize: '32px', | ||
| lineHeight: 1.4, | ||
| color: OG_COLORS.muted, | ||
| }" | ||
| > | ||
| {{ subtitle }} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div :style="{ display: 'flex', height: '12px', width: '180px', backgroundColor: OG_COLORS.accent, borderRadius: '6px' }" /> | ||
| </div> | ||
| </template> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| <script setup lang="ts"> | ||
| import { OG_COLORS, OG_LOGO, ogCardBaseStyle } from './theme' | ||
|
|
||
| withDefaults( | ||
| defineProps<{ | ||
| title?: string | ||
| speakers?: string | ||
| room?: string | ||
| time?: string | ||
| }>(), | ||
| { | ||
| title: '', | ||
| speakers: '', | ||
| room: '', | ||
| time: '', | ||
| }, | ||
| ) | ||
| </script> | ||
|
|
||
| <template> | ||
| <div :style="{ ...ogCardBaseStyle, padding: '72px' }"> | ||
| <div :style="{ display: 'flex', alignItems: 'center' }"> | ||
| <svg | ||
| height="54" | ||
| :viewBox="OG_LOGO.viewBox" | ||
| width="132" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <path | ||
| :d="OG_LOGO.path" | ||
| :fill="OG_COLORS.accent" | ||
| fill-rule="evenodd" | ||
| :transform="OG_LOGO.transform" | ||
| /> | ||
| </svg> | ||
| <span :style="{ marginLeft: '24px', fontSize: '30px', fontWeight: 700, color: OG_COLORS.brand }"> | ||
|
rileychh marked this conversation as resolved.
|
||
| COSCUP 2026 x UbuCon Asia | ||
| </span> | ||
| </div> | ||
|
|
||
| <div | ||
| :style="{ | ||
| display: 'block', | ||
| fontSize: '60px', | ||
| fontWeight: 400, | ||
| lineHeight: 1.25, | ||
| color: OG_COLORS.text, | ||
| }" | ||
| > | ||
| {{ title }} | ||
| </div> | ||
|
|
||
| <div :style="{ display: 'flex', flexDirection: 'column', fontSize: '32px', color: OG_COLORS.brand }"> | ||
| <div | ||
| v-if="speakers" | ||
| :style="{ display: 'flex', alignItems: 'center', color: OG_COLORS.text, fontWeight: 400 }" | ||
| > | ||
| {{ speakers }} | ||
| </div> | ||
| <div | ||
| v-if="room || time" | ||
| :style="{ display: 'flex', marginTop: '12px', color: OG_COLORS.muted }" | ||
| > | ||
| {{ [room, time].filter(Boolean).join(' · ') }} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </template> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // Shared style constants for the OG image templates (Default + Session). | ||
| // Takumi only supports inline styles, so brand colors are duplicated here. | ||
| export const OG_COLORS = { | ||
| background: '#191733', // primary-800 | ||
| brand: '#CCC7FF', // primary-100 | ||
| muted: '#B2ABFE', // primary-200 | ||
| accent: '#3C9838', // cp-green | ||
| text: '#ffffff', | ||
| } | ||
|
|
||
| // Mirrors the fonts in nuxt.config.ts; SC first for widest Han coverage. | ||
| export const OG_FONT_FAMILY = '\'Noto Sans SC\', \'Noto Sans TC\', \'Noto Sans JP\', \'Noto Sans KR\', sans-serif' | ||
|
pan93412 marked this conversation as resolved.
|
||
|
|
||
| // COSCUP wordmark. viewBox/path/transform are fixed; callers set width/height. | ||
| export const OG_LOGO = { | ||
| viewBox: '0 0 92.993 38', | ||
| path: 'M145.315,50.538l-6.406-19.789-3.327,5.666C95.4,17.41,78.007,54.746,78.007,54.746s14.47-27.308,49.942-5.39L124.631,55ZM171,36.779,164.6,17l-3.332,5.641C121.094,3.648,103.7,40.989,103.7,40.989s14.465-27.3,49.941-5.4l-3.327,5.648Z', | ||
| transform: 'translate(-78.007 -17)', | ||
| } | ||
|
|
||
| // Base card layout shared by both templates; callers add their own padding. | ||
| export const ogCardBaseStyle = { | ||
| width: '100%', | ||
| height: '100%', | ||
|
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Both OG templates spread this base style and then add 72–80px of padding on the same root element. Because these templates use inline styles rather than a CSS reset/preflight, the root keeps the default content-box sizing, so Useful? React with 👍 / 👎. |
||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| justifyContent: 'space-between', | ||
| backgroundColor: OG_COLORS.background, | ||
| color: OG_COLORS.text, | ||
| fontFamily: OG_FONT_FAMILY, | ||
| } as const | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,12 +51,10 @@ export default defineNuxtConfig({ | |
| { property: 'og:description', content: DESC }, | ||
| { property: 'og:site_name', content: TITLE }, | ||
| { property: 'og:type', content: 'website' }, | ||
| { property: 'og:image', content: `${URL}/coscup_logo.png` }, | ||
| { name: 'twitter:card', content: 'summary_large_image' }, | ||
| { name: 'twitter:title', content: TITLE }, | ||
| { name: 'twitter:description', content: DESC }, | ||
| { name: 'twitter:site', content: '@coscup' }, | ||
| { name: 'twitter:image', content: `${URL}/coscup_logo.png` }, | ||
| ], | ||
| script: [ | ||
| { | ||
|
|
@@ -96,6 +94,16 @@ export default defineNuxtConfig({ | |
| output: { | ||
| publicDir: process.env.NUXT_OUTPUT_DIR || '.output/public', | ||
| }, | ||
| prerender: { | ||
| concurrency: 4, | ||
| }, | ||
| }, | ||
|
|
||
| experimental: { | ||
| // nuxt-og-image breaks payload-route rendering on pages that call defineOgImage | ||
| // under baseURL "/2026" (its isInternalRoute misses "/2026/.../_payload.json"), | ||
| // making the renderer return no response (500). Disable payload extraction. | ||
| payloadExtraction: false, | ||
|
Comment on lines
+102
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: As of nuxt-og-image version 6.5.2 running on Nuxt 4.4.x, setting experimental.payloadExtraction = false is no longer a documented or required workaround for handling app.baseURL subpaths. The module has received significant updates to its runtime configuration and path resolution logic [1][2][3], specifically improving support for custom baseURLs [4]. In modern versions of the module, the runtime configuration correctly consumes the app's baseURL [1], ensuring that OG image paths are generated with the correct base prefix automatically. If you are encountering issues with OG image path resolution or prerendering while using a subpath (e.g., /2026), ensure you are using the latest version of the module and that your site's baseURL is configured correctly in your nuxt.config.ts: export default defineNuxtConfig({ app: { baseURL: '/2026/', // Ensure trailing slash is included for consistency }, }) There is no supported route-scoped workaround for payload extraction because the issue you are referencing historically stemmed from how Nuxt itself handled base paths during static generation [5][6], which has been largely resolved in later Nuxt versions. If you face persistent 404s or path resolution errors, it is recommended to verify your environment configuration rather than disabling payload extraction, as disabling it may negatively impact performance and functionality [7]. Citations:
🏁 Script executed: git ls-files nuxt.config.ts package.json pnpm-lock.yaml package-lock.json yarn.lock README.md docs 2>/dev/nullRepository: COSCUP/2026 Length of output: 202 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n## files\n'
git ls-files nuxt.config.ts package.json pnpm-lock.yaml package-lock.json yarn.lock README.md docs 2>/dev/null || true
printf '\n## search\n'
rg -n "nuxt-og-image|payloadExtraction|baseURL|app\.baseURL|site\.url|site\.name" nuxt.config.ts package.json README.md docs 2>/dev/null || trueRepository: COSCUP/2026 Length of output: 916 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,220p' nuxt.config.ts | cat -nRepository: COSCUP/2026 Length of output: 7430 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,140p' package.json | cat -nRepository: COSCUP/2026 Length of output: 2103 🌐 Web query:
💡 Result: In nuxt-og-image v6.5.2, configuration and behavior regarding base paths and payloads are handled through Nuxt's standard runtime configuration and internal module routing. Regarding the technical aspects mentioned: 1. payloadExtraction: While not explicitly defined as a top-level module option in documentation, nuxt-og-image relies on Nuxt's built-in payload extraction (used during build/prerendering). The module automatically handles the necessary context for generated images [1][2]. 2. baseURL and subpaths: When deploying a Nuxt application with a custom Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n "payloadExtraction|nuxt-og-image|defineOgImage|baseURL|/2026" README.md nuxt.config.ts pnpm-lock.yamlRepository: COSCUP/2026 Length of output: 1011 Remove the global 🤖 Prompt for AI Agents |
||
| }, | ||
|
|
||
| imports: { | ||
|
|
@@ -122,12 +130,34 @@ export default defineNuxtConfig({ | |
| '@unocss/nuxt', | ||
| '@nuxt/content', | ||
| '@nuxt/eslint', | ||
| '@nuxt/fonts', | ||
| '@nuxt/image', | ||
| '@nuxt/icon', | ||
| '@nuxtjs/i18n', | ||
| 'nuxt-gtag', | ||
| 'nuxt-og-image', | ||
| ], | ||
|
|
||
| // Origin only — app.baseURL ('/2026') is appended automatically by nuxt-site-config. | ||
| site: { | ||
| url: 'https://coscup.org', | ||
| name: TITLE, | ||
| }, | ||
|
|
||
| fonts: { | ||
| // global: true required so nuxt-og-image's renderer can use these fonts. | ||
| // Only weight 400: the Takumi renderer corrupts the 700-weight CJK glyphs | ||
| // (double-draw artifacts), so OG templates render CJK at regular weight. | ||
| // Order matters: Takumi picks one font per script run, so the widest-coverage | ||
| // CJK font (SC) must be first. | ||
| families: [ | ||
| { name: 'Noto Sans SC', weights: [400], global: true }, | ||
| { name: 'Noto Sans TC', weights: [400], global: true }, | ||
| { name: 'Noto Sans JP', weights: [400], global: true }, | ||
| { name: 'Noto Sans KR', weights: [400], global: true }, | ||
| ], | ||
| }, | ||
|
|
||
| content: { | ||
| experimental: { nativeSqlite: true }, | ||
| renderer: { anchorLinks: false }, | ||
|
|
@@ -145,6 +175,8 @@ export default defineNuxtConfig({ | |
| }, | ||
|
|
||
| i18n: { | ||
| // Match site.url so nuxt-site-config can build absolute hreflang/canonical links. | ||
| baseUrl: 'https://coscup.org', | ||
| locales: [ | ||
| { code: 'en', name: 'English', language: 'en-US' }, | ||
| { code: 'zh', name: '中文', language: 'zh-Hant-TW' }, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.