This commit is contained in:
29
app/[locale]/[slug]/opengraph-image.tsx
Normal file
29
app/[locale]/[slug]/opengraph-image.tsx
Normal file
@@ -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(
|
||||||
|
<div style={{ display: 'flex', width: '100%', height: '100%', backgroundColor: '#001a4d' }} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<OGImageTemplate
|
||||||
|
title={pageData.frontmatter.title}
|
||||||
|
description={pageData.frontmatter.excerpt}
|
||||||
|
label="Information"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getPostBySlug } from '@/lib/blog';
|
import { getPostBySlug } from '@/lib/blog';
|
||||||
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
|
|
||||||
export const runtime = 'nodejs';
|
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(
|
return new ImageResponse(
|
||||||
(
|
(
|
||||||
<div
|
<OGImageTemplate
|
||||||
style={{
|
title={post.frontmatter.title}
|
||||||
height: '100%',
|
description={post.frontmatter.excerpt}
|
||||||
width: '100%',
|
label={post.frontmatter.category || 'Blog'}
|
||||||
display: 'flex',
|
image={featuredImage}
|
||||||
flexDirection: 'column',
|
/>
|
||||||
alignItems: 'flex-start',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
backgroundColor: '#001a4d',
|
|
||||||
padding: '80px',
|
|
||||||
position: 'relative',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Background Image Overlay if available */}
|
|
||||||
{post.frontmatter.featuredImage && (
|
|
||||||
<img
|
|
||||||
src={post.frontmatter.featuredImage.startsWith('http') ? post.frontmatter.featuredImage : `https://klz-cables.com${post.frontmatter.featuredImage}`}
|
|
||||||
alt=""
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
inset: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
objectFit: 'cover',
|
|
||||||
opacity: 0.4,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Gradient Overlay */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
inset: 0,
|
|
||||||
background: 'linear-gradient(to top, rgba(0,26,77,1) 0%, rgba(0,26,77,0.4) 100%)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', position: 'relative', zIndex: 10 }}>
|
|
||||||
{post.frontmatter.category && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '20px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#00ff99',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: '0.1em',
|
|
||||||
marginBottom: '16px',
|
|
||||||
backgroundColor: 'rgba(0,255,153,0.1)',
|
|
||||||
padding: '4px 12px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{post.frontmatter.category}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '64px',
|
|
||||||
fontWeight: '900',
|
|
||||||
color: 'white',
|
|
||||||
lineHeight: '1.1',
|
|
||||||
maxWidth: '900px',
|
|
||||||
marginBottom: '24px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{post.frontmatter.title}
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '24px',
|
|
||||||
color: 'rgba(255,255,255,0.6)',
|
|
||||||
fontWeight: '500',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
KLZ Cables Blog
|
|
||||||
</div>
|
|
||||||
<div style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#00ff99', margin: '0 16px' }} />
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '24px',
|
|
||||||
color: 'rgba(255,255,255,0.6)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{new Date(post.frontmatter.date).toLocaleDateString(locale, { year: 'numeric', month: 'long', day: 'numeric' })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
width: 1200,
|
width: 1200,
|
||||||
|
|||||||
25
app/[locale]/contact/opengraph-image.tsx
Normal file
25
app/[locale]/contact/opengraph-image.tsx
Normal file
@@ -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(
|
||||||
|
(
|
||||||
|
<OGImageTemplate
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
label="Contact"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import { OGImageTemplate } from '@/components/OGImageTemplate';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
@@ -8,81 +9,11 @@ export default async function Image({ params: { locale } }: { params: { locale:
|
|||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
(
|
(
|
||||||
<div
|
<OGImageTemplate
|
||||||
style={{
|
title={t('title')}
|
||||||
height: '100%',
|
description={t('description')}
|
||||||
width: '100%',
|
label="Reliable Energy Infrastructure"
|
||||||
display: 'flex',
|
/>
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
justifyContent: 'center',
|
|
||||||
backgroundColor: '#001a4d', // Primary Blue from Styleguide
|
|
||||||
padding: '80px',
|
|
||||||
position: 'relative',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Background Pattern / Scribble placeholder */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-100px',
|
|
||||||
right: '-100px',
|
|
||||||
width: '600px',
|
|
||||||
height: '600px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: 'radial-gradient(circle, rgba(0,255,153,0.1) 0%, transparent 70%)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '24px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#00ff99', // Accent Green
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: '0.2em',
|
|
||||||
marginBottom: '20px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
KLZ Cables
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '72px',
|
|
||||||
fontWeight: '900',
|
|
||||||
color: 'white',
|
|
||||||
lineHeight: '1.1',
|
|
||||||
maxWidth: '800px',
|
|
||||||
marginBottom: '30px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('title')}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '32px',
|
|
||||||
color: 'rgba(255,255,255,0.7)',
|
|
||||||
maxWidth: '700px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('description')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bottom Accent Line */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '80px',
|
|
||||||
left: '80px',
|
|
||||||
width: '120px',
|
|
||||||
height: '8px',
|
|
||||||
backgroundColor: '#00ff99',
|
|
||||||
borderRadius: '4px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
width: 1200,
|
width: 1200,
|
||||||
|
|||||||
62
app/[locale]/products/[...slug]/opengraph-image.tsx
Normal file
62
app/[locale]/products/[...slug]/opengraph-image.tsx
Normal file
@@ -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(
|
||||||
|
(
|
||||||
|
<OGImageTemplate
|
||||||
|
title={categoryTitle}
|
||||||
|
description={categoryDesc}
|
||||||
|
label="Product Category"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const product = await getProductBySlug(productSlug, locale);
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return new ImageResponse(
|
||||||
|
<div style={{ display: 'flex', width: '100%', height: '100%', backgroundColor: '#001a4d' }} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
(
|
||||||
|
<OGImageTemplate
|
||||||
|
title={product.frontmatter.title}
|
||||||
|
description={product.frontmatter.description}
|
||||||
|
label={product.frontmatter.categories?.[0] || 'Product'}
|
||||||
|
image={featuredImage}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
25
app/[locale]/products/opengraph-image.tsx
Normal file
25
app/[locale]/products/opengraph-image.tsx
Normal file
@@ -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(
|
||||||
|
(
|
||||||
|
<OGImageTemplate
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
label="Products"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
26
app/[locale]/team/opengraph-image.tsx
Normal file
26
app/[locale]/team/opengraph-image.tsx
Normal file
@@ -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(
|
||||||
|
(
|
||||||
|
<OGImageTemplate
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
label="Our Team"
|
||||||
|
image="https://klz-cables.com/uploads/2024/12/DSC07655-Large.webp"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
173
components/OGImageTemplate.tsx
Normal file
173
components/OGImageTemplate.tsx
Normal file
@@ -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 (
|
||||||
|
<div style={containerStyle}>
|
||||||
|
{/* Background Image with Overlay */}
|
||||||
|
{image && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={image}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'cover',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
background: 'linear-gradient(to right, rgba(0,26,77,0.9) 0%, rgba(0,26,77,0.4) 100%)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Decorative Scribble Circle (Simplified for Satori) */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-100px',
|
||||||
|
right: '-100px',
|
||||||
|
width: '600px',
|
||||||
|
height: '600px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: `radial-gradient(circle, ${accentGreen}1a 0%, transparent 70%)`,
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', position: 'relative', zIndex: 10 }}>
|
||||||
|
{/* Label / Category */}
|
||||||
|
{label && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: accentGreen,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.2em',
|
||||||
|
marginBottom: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '72px',
|
||||||
|
fontWeight: '900',
|
||||||
|
color: 'white',
|
||||||
|
lineHeight: '1.1',
|
||||||
|
maxWidth: '900px',
|
||||||
|
marginBottom: '32px',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
{description && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '32px',
|
||||||
|
color: 'rgba(255,255,255,0.8)',
|
||||||
|
maxWidth: '800px',
|
||||||
|
lineHeight: '1.4',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Brand Footer */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '80px',
|
||||||
|
left: '80px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '120px',
|
||||||
|
height: '8px',
|
||||||
|
backgroundColor: accentGreen,
|
||||||
|
borderRadius: '4px',
|
||||||
|
marginRight: '24px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'white',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.1em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
KLZ Cables
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Saturated Blue Accent */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
width: '10px',
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: saturatedBlue,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user