İçeriğe geç
nextjs

Next.js Dynamic OG Image Oluşturma

Tarık Tunç
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} &bull; {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`],
    },
  };
}
İpucu: OG image'leri test etmek için şu araçları kullanabilirsiniz: opengraph.xyz ile önizleme, Twitter Card Validator, Facebook Sharing Debugger ve LinkedIn Post Inspector. Doğrudan API route'unuzu tarayıcıda açarak da görseli görebilirsiniz.

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 }
  );
}
Uyarı: OG image route'ları Edge Runtime'da çalışır. Bu, Node.js API'lerinin tamamının mevcut olmadığı anlamına gelir. Dosya sistemi erişimi, bazı npm paketleri ve Node.js-specific modüller Edge Runtime'da çalışmaz. Satori (ImageResponse'un kullandığı motor), CSS'in sınırlı bir alt kümesini destekler ve Flexbox tabanlı layout kullanır.

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.

Kaynaklar

İlgili Yazılar