From 13ab4bde75c43d5f2d2cc247c4f7a21c9e9953ff Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Tue, 27 Jan 2026 00:10:10 +0100 Subject: [PATCH] og image --- app/[locale]/[slug]/opengraph-image.tsx | 29 +++ app/[locale]/blog/[slug]/opengraph-image.tsx | 103 ++--------- app/[locale]/contact/opengraph-image.tsx | 25 +++ app/[locale]/opengraph-image.tsx | 81 +------- .../products/[...slug]/opengraph-image.tsx | 62 +++++++ app/[locale]/products/opengraph-image.tsx | 25 +++ app/[locale]/team/opengraph-image.tsx | 26 +++ components/OGImageTemplate.tsx | 173 ++++++++++++++++++ 8 files changed, 359 insertions(+), 165 deletions(-) create mode 100644 app/[locale]/[slug]/opengraph-image.tsx create mode 100644 app/[locale]/contact/opengraph-image.tsx create mode 100644 app/[locale]/products/[...slug]/opengraph-image.tsx create mode 100644 app/[locale]/products/opengraph-image.tsx create mode 100644 app/[locale]/team/opengraph-image.tsx create mode 100644 components/OGImageTemplate.tsx diff --git a/app/[locale]/[slug]/opengraph-image.tsx b/app/[locale]/[slug]/opengraph-image.tsx new file mode 100644 index 00000000..bf7b6cfc --- /dev/null +++ b/app/[locale]/[slug]/opengraph-image.tsx @@ -0,0 +1,29 @@ +import { ImageResponse } from 'next/og'; +import { getPageBySlug } from '@/lib/pages'; +import { OGImageTemplate } from '@/components/OGImageTemplate'; + +export const runtime = 'nodejs'; + +export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug: string } }) { + const pageData = await getPageBySlug(slug, locale); + + if (!pageData) { + return new ImageResponse( +
+ ); + } + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); +} diff --git a/app/[locale]/blog/[slug]/opengraph-image.tsx b/app/[locale]/blog/[slug]/opengraph-image.tsx index 13ee2e7c..a12d7f26 100644 --- a/app/[locale]/blog/[slug]/opengraph-image.tsx +++ b/app/[locale]/blog/[slug]/opengraph-image.tsx @@ -1,5 +1,6 @@ import { ImageResponse } from 'next/og'; import { getPostBySlug } from '@/lib/blog'; +import { OGImageTemplate } from '@/components/OGImageTemplate'; export const runtime = 'nodejs'; @@ -12,98 +13,20 @@ export default async function Image({ params: { locale, slug } }: { params: { lo ); } + const featuredImage = post.frontmatter.featuredImage + ? (post.frontmatter.featuredImage.startsWith('http') + ? post.frontmatter.featuredImage + : `https://klz-cables.com${post.frontmatter.featuredImage}`) + : undefined; + return new ImageResponse( ( -
- {/* Background Image Overlay if available */} - {post.frontmatter.featuredImage && ( - - )} - - {/* Gradient Overlay */} -
- -
- {post.frontmatter.category && ( -
- {post.frontmatter.category} -
- )} -
- {post.frontmatter.title} -
-
-
- KLZ Cables Blog -
-
-
- {new Date(post.frontmatter.date).toLocaleDateString(locale, { year: 'numeric', month: 'long', day: 'numeric' })} -
-
-
-
+ ), { width: 1200, diff --git a/app/[locale]/contact/opengraph-image.tsx b/app/[locale]/contact/opengraph-image.tsx new file mode 100644 index 00000000..d7a7d44f --- /dev/null +++ b/app/[locale]/contact/opengraph-image.tsx @@ -0,0 +1,25 @@ +import { ImageResponse } from 'next/og'; +import { getTranslations } from 'next-intl/server'; +import { OGImageTemplate } from '@/components/OGImageTemplate'; + +export const runtime = 'nodejs'; + +export default async function Image({ params: { locale } }: { params: { locale: string } }) { + const t = await getTranslations({ locale, namespace: 'Contact' }); + const title = t('meta.title') || t('title'); + const description = t('meta.description') || t('subtitle'); + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); +} diff --git a/app/[locale]/opengraph-image.tsx b/app/[locale]/opengraph-image.tsx index 5cd03e7f..8a405205 100644 --- a/app/[locale]/opengraph-image.tsx +++ b/app/[locale]/opengraph-image.tsx @@ -1,5 +1,6 @@ import { ImageResponse } from 'next/og'; import { getTranslations } from 'next-intl/server'; +import { OGImageTemplate } from '@/components/OGImageTemplate'; export const runtime = 'edge'; @@ -8,81 +9,11 @@ export default async function Image({ params: { locale } }: { params: { locale: return new ImageResponse( ( -
- {/* Background Pattern / Scribble placeholder */} -
- -
-
- KLZ Cables -
-
- {t('title')} -
-
- {t('description')} -
-
- - {/* Bottom Accent Line */} -
-
+ ), { width: 1200, diff --git a/app/[locale]/products/[...slug]/opengraph-image.tsx b/app/[locale]/products/[...slug]/opengraph-image.tsx new file mode 100644 index 00000000..54812d1a --- /dev/null +++ b/app/[locale]/products/[...slug]/opengraph-image.tsx @@ -0,0 +1,62 @@ +import { ImageResponse } from 'next/og'; +import { getProductBySlug } from '@/lib/mdx'; +import { getTranslations } from 'next-intl/server'; +import { OGImageTemplate } from '@/components/OGImageTemplate'; + +export const runtime = 'nodejs'; + +export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug: string[] } }) { + const productSlug = slug[slug.length - 1]; + const t = await getTranslations('Products'); + + // Check if it's a category page + const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables']; + if (categories.includes(productSlug)) { + const categoryKey = productSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase()); + const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : productSlug; + const categoryDesc = t.has(`categories.${categoryKey}.description`) ? t(`categories.${categoryKey}.description`) : ''; + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); + } + + const product = await getProductBySlug(productSlug, locale); + + if (!product) { + return new ImageResponse( +
+ ); + } + + const featuredImage = product.frontmatter.images?.[0] + ? (product.frontmatter.images[0].startsWith('http') + ? product.frontmatter.images[0] + : `https://klz-cables.com${product.frontmatter.images[0]}`) + : undefined; + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); +} diff --git a/app/[locale]/products/opengraph-image.tsx b/app/[locale]/products/opengraph-image.tsx new file mode 100644 index 00000000..1db211fc --- /dev/null +++ b/app/[locale]/products/opengraph-image.tsx @@ -0,0 +1,25 @@ +import { ImageResponse } from 'next/og'; +import { getTranslations } from 'next-intl/server'; +import { OGImageTemplate } from '@/components/OGImageTemplate'; + +export const runtime = 'nodejs'; + +export default async function Image({ params: { locale } }: { params: { locale: string } }) { + const t = await getTranslations({ locale, namespace: 'Products' }); + const title = t('meta.title') || t('title'); + const description = t('meta.description') || t('subtitle'); + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); +} diff --git a/app/[locale]/team/opengraph-image.tsx b/app/[locale]/team/opengraph-image.tsx new file mode 100644 index 00000000..a9decaad --- /dev/null +++ b/app/[locale]/team/opengraph-image.tsx @@ -0,0 +1,26 @@ +import { ImageResponse } from 'next/og'; +import { getTranslations } from 'next-intl/server'; +import { OGImageTemplate } from '@/components/OGImageTemplate'; + +export const runtime = 'nodejs'; + +export default async function Image({ params: { locale } }: { params: { locale: string } }) { + const t = await getTranslations({ locale, namespace: 'Team' }); + const title = t('meta.title') || t('hero.subtitle'); + const description = t('meta.description') || t('hero.title'); + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); +} diff --git a/components/OGImageTemplate.tsx b/components/OGImageTemplate.tsx new file mode 100644 index 00000000..4ea8eb6a --- /dev/null +++ b/components/OGImageTemplate.tsx @@ -0,0 +1,173 @@ +import React from 'react'; + +interface OGImageTemplateProps { + title: string; + description?: string; + label?: string; + image?: string; + mode?: 'dark' | 'light' | 'image'; +} + +export function OGImageTemplate({ + title, + description, + label, + image, + mode = 'dark', +}: OGImageTemplateProps) { + const primaryBlue = '#001a4d'; + const accentGreen = '#82ed20'; + const saturatedBlue = '#011dff'; + + const containerStyle: React.CSSProperties = { + height: '100%', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'center', + backgroundColor: mode === 'light' ? '#ffffff' : primaryBlue, + padding: '80px', + position: 'relative', + fontFamily: 'Inter, sans-serif', + }; + + return ( +
+ {/* Background Image with Overlay */} + {image && ( +
+ +
+
+ )} + + {/* Decorative Scribble Circle (Simplified for Satori) */} +
+ +
+ {/* Label / Category */} + {label && ( +
+ {label} +
+ )} + + {/* Title */} +
+ {title} +
+ + {/* Description */} + {description && ( +
+ {description} +
+ )} +
+ + {/* Brand Footer */} +
+
+
+ KLZ Cables +
+
+ + {/* Saturated Blue Accent */} +
+
+ ); +}