feat(og): add logo and dynamic faded background image to OG templates
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 11s
Build & Deploy / 🧪 QA (push) Successful in 1m31s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled

This commit is contained in:
2026-06-22 11:41:32 +02:00
parent bff150b1a7
commit 2c35c6b8aa
9 changed files with 101 additions and 25 deletions

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getPageBySlug } from '@/lib/pages';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -20,9 +20,12 @@ export default async function Image({
}
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
return new ImageResponse(
<OGImageTemplate
image={bgImage}
logo={logo}
title={pageData.frontmatter.title}
description={pageData.frontmatter.excerpt}
label="Information"

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getPostBySlug } from '@/lib/blog';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
import { SITE_URL } from '@/lib/schema';
export const size = OG_IMAGE_SIZE;
@@ -35,6 +35,7 @@ export default async function Image({
}
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
// We don't have request.url here, but we can assume the domain from SITE_URL or config
// For local images during dev, relative paths in <img> might not work in Satori
@@ -55,10 +56,11 @@ export default async function Image({
return new ImageResponse(
<OGImageTemplate
logo={logo}
title={post.frontmatter.title}
description={post.frontmatter.excerpt}
label={post.frontmatter.category || 'Blog'}
image={base64Image || featuredImage}
image={base64Image || featuredImage || bgImage}
/>,
{
...OG_IMAGE_SIZE,

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,9 +11,16 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Blog.meta' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
return new ImageResponse(
<OGImageTemplate title={t('title')} description={t('description')} label="Blog" />,
<OGImageTemplate
image={bgImage}
logo={logo}
title={t('title')}
description={t('description')}
label="Blog"
/>,
{
...OG_IMAGE_SIZE,
fonts,

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,12 +11,19 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Contact' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
const title = t('meta.title') || t('title');
const description = t('meta.description') || t('subtitle');
return new ImageResponse(
<OGImageTemplate title={title} description={description} label="Contact" />,
<OGImageTemplate
image={bgImage}
logo={logo}
title={title}
description={description}
label="Contact"
/>,
{
...OG_IMAGE_SIZE,
fonts,

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,9 +11,12 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Index.meta' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
return new ImageResponse(
<OGImageTemplate
image={bgImage}
logo={logo}
title={t('title')}
description={t('description')}
label="Reliable Energy Infrastructure"

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,6 +11,7 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Products' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
const title = t.has('meta.title')
? t('meta.title')
@@ -20,7 +21,13 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const description = t('meta.description') || t('subtitle');
return new ImageResponse(
<OGImageTemplate title={title} description={description} label="Products" />,
<OGImageTemplate
image={bgImage}
logo={logo}
title={title}
description={description}
label="Products"
/>,
{
...OG_IMAGE_SIZE,
fonts,

View File

@@ -1,7 +1,7 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { getOgFonts, OG_IMAGE_SIZE } from '@/lib/og-helper';
import { getOgFonts, OG_IMAGE_SIZE, getOgAssets } from '@/lib/og-helper';
export const size = OG_IMAGE_SIZE;
export const contentType = 'image/png';
@@ -11,12 +11,19 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Team' });
const fonts = await getOgFonts();
const { logo, bgImage } = getOgAssets();
const title = t('meta.title') || t('hero.subtitle');
const description = t('meta.description') || t('hero.title');
return new ImageResponse(
<OGImageTemplate title={title} description={description} label="Our Team" />,
<OGImageTemplate
image={bgImage}
logo={logo}
title={title}
description={description}
label="Our Team"
/>,
{
...OG_IMAGE_SIZE,
fonts,

View File

@@ -5,6 +5,7 @@ interface OGImageTemplateProps {
description?: string;
label?: string;
image?: string;
logo?: string;
mode?: 'dark' | 'light' | 'image';
}
@@ -13,6 +14,7 @@ export function OGImageTemplate({
description,
label,
image,
logo,
mode = 'dark',
}: OGImageTemplateProps) {
const primaryBlue = '#001a4d';
@@ -152,21 +154,26 @@ export function OGImageTemplate({
height: '6px',
backgroundColor: accentGreen,
borderRadius: '3px',
marginRight: '24px',
marginRight: '32px',
}}
/>
<div
style={{
fontSize: '24px',
fontWeight: 700,
color: 'white',
textTransform: 'uppercase',
letterSpacing: '0.15em',
display: 'flex',
}}
>
KLZ Cables
</div>
{logo ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img src={logo} alt="KLZ Cables" height="40" style={{ objectFit: 'contain' }} />
) : (
<div
style={{
fontSize: '24px',
fontWeight: 700,
color: 'white',
textTransform: 'uppercase',
letterSpacing: '0.15em',
display: 'flex',
}}
>
KLZ Cables
</div>
)}
</div>
{/* Saturated Blue Brand Strip */}

View File

@@ -55,3 +55,36 @@ export const OG_IMAGE_SIZE = {
width: 1200,
height: 630,
};
/**
* Loads a local image and converts it to a base64 data URI for Satori
*/
export function getLocalImageBase64(filename: string): string | undefined {
try {
const filePath = join(process.cwd(), 'public', filename);
const buffer = readFileSync(filePath);
const ext = filename.split('.').pop()?.toLowerCase();
const mimeType =
ext === 'png'
? 'image/png'
: ext === 'jpg' || ext === 'jpeg'
? 'image/jpeg'
: 'image/svg+xml';
return `data:${mimeType};base64,${buffer.toString('base64')}`;
} catch (error) {
console.error(`[OG] Failed to load local image ${filename}:`, error);
return undefined;
}
}
/**
* Returns standard assets for OG images
*/
export function getOgAssets() {
return {
logo: getLocalImageBase64('logo-white.png'),
bgImage: getLocalImageBase64(
'media/aerial-view-of-electricity-station-surrounded-with-2023-11-27-05-33-40-utc-scaled.jpg',
),
};
}