Circle IT Logo
Technology

Cara Generate Open Graph Image Dinamis dengan Next.js 14

Pelajari cara membuat Open Graph images secara dinamis menggunakan fitur terbaru Next.js 14. Tingkatkan SEO dan social media presence website Anda dengan thumbnail yang menarik dan otomatis.

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:

bash
npx create-next-app@latest my-og-project
cd my-og-project

Method 1: Image Response API

Next.js 14 menyediakan ImageResponse API yang memungkinkan kita generate gambar menggunakan JSX.

Basic Implementation

tsx
// 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

tsx
// 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

tsx
// 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

tsx
// 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:

tsx
// 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:

tsx
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

tsx
// Enable edge runtime for faster response
export const runtime = 'edge';

// Cache the response
export const revalidate = 3600; // 1 hour

3. Error Handling

tsx
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:

  1. Facebook Sharing Debugger: https://developers.facebook.com/tools/debug/
  2. Twitter Card Validator: https://cards-dev.twitter.com/validator
  3. LinkedIn Post Inspector: https://www.linkedin.com/post-inspector/

Template Lanjutan

Blog Post Template

tsx
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.

Resources