In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
When sharing links on social media (Facebook, Twitter, LinkedIn), the preview card (image, title, description) heavily influences click-through rates. Most websites show static OG images that don't reflect the actual page content, missing opportunities for engagement.
What we want to achieve:
Dynamic OG images tailored to each page type (homepage, blog posts, services, etc.)
// Define supported page typesexporttypePageType = 'homepage' | 'service' | 'blog' | 'blogArticle' | 'about' | 'contact';
exportinterfaceOgImageParams {
type?: PageType;
title: string;
subtitle?: string;
image?: string; // filename relative to public folder
}
/**
* Generates a URL for dynamic OG image generation
* @paramparams - Configuration for the OG image
* @returns Complete URL for the OG image endpoint
*/exportfunctiongetOgImageUrl({
type = 'homepage',
title,
subtitle,
image
}: OgImageParams): string {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://yourdomain.com';
const endpoint = `${baseUrl}/api/og`;
const params = newURLSearchParams({
type: type.toString(),
title: title.trim(),
});
if (subtitle?.trim()) {
params.append('subtitle', subtitle.trim());
}
if (image?.trim()) {
params.append('image', image.trim());
}
return`${endpoint}?${params.toString()}`;
}
/**
* Utility to truncate text for better OG image display
*/exportfunctiontruncateText(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength).trim() + '...';
}
/**
* Generate metadata object with OG image for Next.js generateMetadata
*/exportfunctiongenerateOgMetadata({
title,
description,
ogImageParams
}: {
title: string;
description: string;
ogImageParams: OgImageParams;
}) {
return {
title,
description,
openGraph: {
title,
description,
images: [
{
url: getOgImageUrl(ogImageParams),
width: 1200,
height: 630,
alt: title,
},
],
},
twitter: {
card: 'summary_large_image',
title,
description,
images: [getOgImageUrl(ogImageParams)],
},
};
}
Step 4: Implement in Different Page Types
Homepage Example
app/page.tsx
tsx
import { generateOgMetadata } from'@/lib/og-image';
exportasyncfunctiongenerateMetadata() {
returngenerateOgMetadata({
title: 'Your Business Name - Professional Services',
description: 'We provide top-quality services to help your business grow and succeed.',
ogImageParams: {
type: 'homepage',
title: 'Your Business Name',
subtitle: 'Professional Services That Deliver Results',
image: 'hero-background.jpg', // Optional: file in /public folder
},
});
}
exportdefaultfunctionHomePage() {
return (
<div><h1>Welcome to Your Business</h1>
{/* Your homepage content */}
</div>
);
}
Blog Article Example
app/blog/[slug]/page.tsx
tsx
import { generateOgMetadata, truncateText } from'@/lib/og-image';
// Mock function - replace with your actual blog post fetching logicasyncfunctiongetBlogPost(slug: string) {
// Your blog post fetching logic herereturn {
title: 'Understanding ADHD: A Complete Guide',
excerpt: 'Learn about ADHD symptoms, diagnosis, and treatment options in this comprehensive guide.',
author: 'Dr. Jane Smith',
publishedAt: '2024-01-15',
featuredImage: 'adhd-guide-bg.jpg',
content: '...',
};
}
exportasyncfunctiongenerateMetadata({
params
}: {
params: { slug: string }
}) {
const post = awaitgetBlogPost(params.slug);
returngenerateOgMetadata({
title: post.title,
description: post.excerpt,
ogImageParams: {
type: 'blogArticle',
title: truncateText(post.title, 60), // Ensure it fits nicelysubtitle: truncateText(post.excerpt, 120),
image: post.featuredImage,
},
});
}
exportdefaultasyncfunctionBlogPostPage({
params
}: {
params: { slug: string }
}) {
const post = awaitgetBlogPost(params.slug);
return (
<article><h1>{post.title}</h1><p>{post.excerpt}</p>
{/* Your blog post content */}
</article>
);
}
Service Page Example
app/services/[serviceSlug]/page.tsx
tsx
import { generateOgMetadata } from'@/lib/og-image';
const services = {
'web-development': {
title: 'Web Development Services',
description: 'Custom web development solutions for modern businesses.',
features: ['Responsive Design', 'Performance Optimization', 'SEO Ready'],
backgroundImage: 'web-dev-bg.jpg',
},
'digital-marketing': {
title: 'Digital Marketing Services',
description: 'Grow your online presence with our digital marketing expertise.',
features: ['SEO', 'Social Media', 'Content Marketing'],
backgroundImage: 'marketing-bg.jpg',
},
};
exportasyncfunctiongenerateMetadata({
params
}: {
params: { serviceSlug: string }
}) {
const service = services[params.serviceSlugas keyof typeof services];
if (!service) {
return { title: 'Service Not Found' };
}
returngenerateOgMetadata({
title: service.title,
description: service.description,
ogImageParams: {
type: 'service',
title: service.title,
subtitle: service.description,
image: service.backgroundImage,
},
});
}
exportdefaultfunctionServicePage({
params
}: {
params: { serviceSlug: string }
}) {
const service = services[params.serviceSlugas keyof typeof services];
if (!service) {
return<div>Service not found</div>;
}
return (
<div><h1>{service.title}</h1><p>{service.description}</p>
{/* Your service page content */}
</div>
);
}
Step 5: Environment Configuration
Add to your .env.local:
env
NEXT_PUBLIC_BASE_URL=https://yourdomain.com
# For local development, use: http://localhost:3000