Next.js Dynamic OG Image Oluşturma

Next.js Dynamic OG Image Oluşturma
Open Graph (OG) görselleri, sosyal medyada paylaşıldığında içeriğinizin nasıl görüneceğini belirler. Statik görseller yerine dinamik OG görselleri oluşturarak her sayfa için özelleştirilmiş, çekici paylaşım kartları üretebilirsiniz. Next.js, @vercel/og kütüphanesi ve yerleşik ImageResponse API'si ile bu süreci büyük ölçüde kolaylaştırır.
OG Image Nedir ve Neden Önemli?
OG Image, bir URL sosyal medyada (Twitter, Facebook, LinkedIn, WhatsApp) paylaşıldığında gösterilen önizleme görseridir. Önerilen boyut 1200x630 piksel'dir.
- Paylaşımların tıklanma oranını (CTR) artırır
- İçeriğin profesyonel görünmesini sağlar
- Her sayfa için otomatik, tutarlı görseller üretir
- Manuel görsel tasarım ihtiyacını ortadan kaldırır
Kurulum
Next.js 14+ sürümlerinde ImageResponse doğrudan next/og'dan import edilebilir:
// next/og kullanımı (Next.js 14+)
import { ImageResponse } from 'next/og';
// Alternatif: @vercel/og paketi
npm install @vercel/og
Temel OG Image Route
// app/api/og/route.jsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Varsayılan Başlık';
const description = searchParams.get('description') || '';
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#0f172a',
color: '#ffffff',
padding: '60px',
}}
>
<h1 style={{ fontSize: 60, fontWeight: 'bold', textAlign: 'center' }}>
{title}
</h1>
{description && (
<p style={{ fontSize: 28, color: '#94a3b8', marginTop: 20 }}>
{description}
</p>
)}
<div style={{ marginTop: 40, fontSize: 24, color: '#3b82f6' }}>
example.com
</div>
</div>
),
{
width: 1200,
height: 630,
}
);
}
opengraph-image.jsx Convention
Next.js App Router'da opengraph-image.jsx dosya adını kullanarak her route segment için otomatik OG image oluşturabilirsiniz:
// app/opengraph-image.jsx — Ana sayfa OG görseli
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export const runtime = 'edge';
export const alt = 'Web Geliştirme Blogu';
export default async function OGImage() {
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
fontFamily: 'sans-serif',
}}
>
<div style={{ fontSize: 72, fontWeight: 'bold' }}>
Web Dev Blog
</div>
<div style={{ fontSize: 32, marginTop: 20, opacity: 0.8 }}>
Modern web teknolojileri hakkında yazılar
</div>
</div>
),
{ ...size }
);
}
Blog Yazıları İçin Dinamik OG Image
// app/blog/[slug]/opengraph-image.jsx
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export const runtime = 'edge';
export default async function BlogOGImage({ params }) {
const { slug } = await params;
// Blog yazısı verilerini çek
const post = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/posts/${slug}`
).then((res) => res.json());
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '60px',
backgroundColor: '#1a1a2e',
color: '#ffffff',
}}
>
{/* Kategori badge */}
<div
style={{
display: 'flex',
fontSize: 20,
color: '#818cf8',
textTransform: 'uppercase',
letterSpacing: 2,
}}
>
{post.category}
</div>
{/* Başlık */}
<div
style={{
display: 'flex',
fontSize: 56,
fontWeight: 'bold',
lineHeight: 1.2,
maxWidth: '80%',
}}
>
{post.title}
</div>
{/* Footer */}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<div style={{ fontSize: 22, color: '#94a3b8' }}>
{post.author} • {post.readTime} dk okuma
</div>
</div>
<div style={{ fontSize: 24, color: '#3b82f6' }}>
example.com
</div>
</div>
</div>
),
{ ...size }
);
}
export function generateImageMetadata({ params }) {
return [{ id: params.slug, alt: `Blog: ${params.slug}` }];
}
Custom Font Kullanımı
// app/api/og/route.jsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request) {
// Google Fonts'tan font yükle
const interBold = await fetch(
new URL('https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYAZ9hjQ.ttf')
).then((res) => res.arrayBuffer());
// Lokal font dosyası
const customFont = await fetch(
new URL('../../assets/fonts/CustomFont-Bold.ttf', import.meta.url)
).then((res) => res.arrayBuffer());
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Başlık';
return new ImageResponse(
(
<div style={{
width: '100%', height: '100%',
display: 'flex', alignItems: 'center', justifyContent: 'center',
backgroundColor: '#000', color: '#fff',
fontFamily: 'Inter',
}}>
<h1 style={{ fontSize: 64 }}>{title}</h1>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{ name: 'Inter', data: interBold, weight: 700 },
{ name: 'Custom', data: customFont, weight: 700 },
],
}
);
}
Metadata ile Entegrasyon
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.createdAt,
authors: [post.author],
images: [
{
url: `/blog/${slug}/opengraph-image`,
width: 1200,
height: 630,
alt: post.title,
},
],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [`/blog/${slug}/opengraph-image`],
},
};
}
Twitter Card Desteği
// app/blog/[slug]/twitter-image.jsx
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function TwitterImage({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return new ImageResponse(
(
<div style={{
width: '100%', height: '100%',
display: 'flex', flexDirection: 'column',
justifyContent: 'center', padding: 60,
backgroundColor: '#15202b', color: '#ffffff',
}}>
<div style={{ fontSize: 52, fontWeight: 'bold' }}>
{post.title}
</div>
<div style={{ fontSize: 24, marginTop: 16, color: '#8899a6' }}>
@username
</div>
</div>
),
{ ...size }
);
}
Performans İpuçları
- Font dosyalarını mümkünse cache'leyin
- Karmaşık layout'lardan kaçının — basit tutun
- Harici resim kullanıyorsanız boyutlarını küçük tutun
- CDN cache header'ları ayarlayın
Sonuç
Next.js'te dinamik OG image oluşturma, sosyal medya paylaşımlarınızın görünürlüğünü ve tıklanma oranını artırmanın etkili bir yoludur. opengraph-image.jsx convention'ı ile her route segment için otomatik görseller üretebilir, custom font'lar ekleyebilir ve metadata ile entegre edebilirsiniz. Edge Runtime'da çalışan bu görseller hızlı üretilir ve CDN üzerinden cache'lenir. Sosyal medya test araçlarıyla görsellerin doğru şekilde gösterildiğini doğrulamayı unutmayın.