Pengenalan
Open Graph (OG) images adalah gambar yang muncul ketika Anda share link di social media seperti Facebook, Twitter, LinkedIn, atau WhatsApp. Dengan Next.js 14, kita dapat generate OG images secara dinamis tanpa perlu membuat gambar manual untuk setiap halaman.
Mengapa Dynamic OG Images Penting?
- SEO: Meningkatkan click-through rate di search results
- Social Sharing: Membuat link lebih menarik ketika di-share
- Branding: Konsistensi visual brand di semua platform
- Automation: Tidak perlu create gambar manual untuk setiap konten
Setup Project
Pastikan Anda menggunakan Next.js 14 atau lebih baru:
npx create-next-app@latest my-og-project
cd my-og-projectMethod 1: Image Response API
Next.js 14 menyediakan ImageResponse API yang memungkinkan kita generate gambar menggunakan JSX.
Basic Implementation
// app/api/og/route.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Default Title';
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{title}
</div>
),
{
width: 1200,
height: 630,
},
);
}Advanced Styling
// app/api/og/route.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Default Title';
const description = searchParams.get('description') || '';
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#1e293b',
backgroundImage: 'linear-gradient(to bottom right, #1e293b, #334155)',
padding: '40px 80px',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: '20px',
}}
>
<h1
style={{
fontSize: 60,
fontWeight: 'bold',
color: 'white',
textAlign: 'center',
lineHeight: 1.2,
}}
>
{title}
</h1>
{description && (
<p
style={{
fontSize: 24,
color: '#94a3b8',
textAlign: 'center',
maxWidth: '80%',
}}
>
{description}
</p>
)}
</div>
</div>
),
{
width: 1200,
height: 630,
},
);
}Method 2: Metadata API
Next.js 14 juga menyediakan Metadata API untuk set OG images lebih mudah.
Static Metadata
// app/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'My Page',
description: 'Page description',
openGraph: {
title: 'My Page',
description: 'Page description',
images: [
{
url: '/api/og?title=My Page',
width: 1200,
height: 630,
alt: 'My Page OG Image',
},
],
},
};
export default function Page() {
return <div>Content</div>;
}Dynamic Metadata
// app/blog/[slug]/page.tsx
import { Metadata } from 'next';
interface Props {
params: { slug: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
// Fetch post data
const post = await getPost(params.slug);
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
images: [
{
url: `/api/og?title=${encodeURIComponent(post.title)}&description=${encodeURIComponent(post.description)}`,
width: 1200,
height: 630,
},
],
},
};
}
export default function BlogPost({ params }: Props) {
return <div>Blog content</div>;
}Custom Fonts
Untuk menggunakan custom fonts di OG images:
// app/api/og/route.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request: Request) {
// Load font
const interBold = await fetch(
new URL('./Inter-Bold.ttf', import.meta.url)
).then((res) => res.arrayBuffer());
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Default Title';
return new ImageResponse(
(
<div
style={{
fontSize: 60,
fontFamily: 'Inter',
fontWeight: 700,
color: 'white',
background: 'linear-gradient(to bottom right, #1e293b, #334155)',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '80px',
}}
>
{title}
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: interBold,
style: 'normal',
weight: 700,
},
],
},
);
}Images dan Icons
Untuk menambahkan images atau icons:
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Default Title';
return new ImageResponse(
(
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
height: '100%',
padding: '80px',
backgroundColor: 'white',
}}
>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<h1 style={{ fontSize: 60, fontWeight: 'bold' }}>{title}</h1>
</div>
<img
src="https://your-domain.com/logo.png"
alt="Logo"
width={200}
height={200}
/>
</div>
),
{
width: 1200,
height: 630,
},
);
}Best Practices
1. Optimal Dimensions
- Facebook/LinkedIn: 1200 x 630 pixels
- Twitter: 1200 x 600 pixels (aspect ratio 2:1)
2. Performance Optimization
// Enable edge runtime for faster response
export const runtime = 'edge';
// Cache the response
export const revalidate = 3600; // 1 hour3. Error Handling
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title');
if (!title) {
return new Response('Title parameter is required', { status: 400 });
}
return new ImageResponse(
// ... your JSX
{
width: 1200,
height: 630,
},
);
} catch (error) {
return new Response('Failed to generate image', { status: 500 });
}
}Testing
Gunakan tools berikut untuk test OG images:
- Facebook Sharing Debugger: https://developers.facebook.com/tools/debug/
- Twitter Card Validator: https://cards-dev.twitter.com/validator
- LinkedIn Post Inspector: https://www.linkedin.com/post-inspector/
Template Lanjutan
Blog Post Template
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Blog Post';
const author = searchParams.get('author') || 'Author';
const date = searchParams.get('date') || new Date().toLocaleDateString();
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#0f172a',
padding: '60px 80px',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 40 }}>
<div style={{ fontSize: 24, color: '#94a3b8' }}>Blog</div>
<div style={{ fontSize: 24, color: '#94a3b8' }}>{date}</div>
</div>
<h1
style={{
fontSize: 72,
fontWeight: 'bold',
color: 'white',
lineHeight: 1.1,
marginBottom: 40,
}}
>
{title}
</h1>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<div
style={{
width: 60,
height: 60,
borderRadius: '50%',
backgroundColor: '#3b82f6',
}}
/>
<span style={{ fontSize: 28, color: '#e2e8f0' }}>{author}</span>
</div>
</div>
),
{
width: 1200,
height: 630,
},
);
}Kesimpulan
Dynamic OG images dengan Next.js 14 memberikan cara yang powerful dan efficient untuk improve social sharing dan SEO website Anda. Dengan ImageResponse API dan Metadata API, Anda dapat create professional-looking OG images tanpa perlu design tools atau manual image creation.