mcpField
O
og-image

og-image

github.com/tushaarmehtaaMarketing & growth1247 installsUpdated 2w ago
Repository

About og-image

A web-development skill for adding dynamic Open Graph images and social metadata across a site. It provides setup guidance for several frameworks and static HTML, along with design and preview checks.

Key features

  1. 01Detect frameworks and existing metadata
  2. 02Set up dynamic OG image routes
  3. 03Add page-level Open Graph and Twitter tags
  4. 04Guide image layout for feed previews
  5. 05Verify previews with platform validators

Use cases

  1. 01Add share images to a Next.js site
  2. 02Create per-page previews for dynamic content
  3. 03Set social metadata on Astro or static pages
  4. 04Diagnose missing or broken link previews

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/tushaarmehtaa/tushar-skills --skill og-image

After installing, type/og-imagein Claude Code to confirm it loaded.

Details

From the project README

Dynamic OG image generation and the full meta tag stack — one route, all pages covered. Your links stop looking broken when someone shares them.

Phase 1: Detect the Stack

Check the codebase:

  • Framework: Next.js App Router / Pages Router / Astro / Remix / static HTML?
  • Existing meta tags: Search for og:image, twitter:card in layout files
  • Dynamic pages: Blog posts, product pages, skill pages — anything that needs per-page OG?

If meta tags already exist, audit them before changing anything.

Phase 2: Framework-Specific Setup

Next.js App Router (recommended path)

Create 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') || '[Project Name]';
  const description = searchParams.get('description') || '[One-line description]';

  return new ImageResponse(
    (
      <div
        style={{
          height: '100%',
          width: '100%',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'center',
          backgroundColor: '#09090b',
          color: '#fafafa',
          fontFamily: 'system-ui, sans-serif',
          padding: '60px',
        }}
      >
        <div
          style={{
            fontSize: 64,
            fontWeight: 'bold',
            marginBottom: 24,
            textAlign: 'center',
            lineHeight: 1.1,
          }}
        >
          {title}
        </div>
        <div
          style={{
            fontSize: 28,
            opacity: 0.55,
            textAlign: 'center',
            maxWidth: '80%',
            lineHeight: 1.4,
          }}
        >
          {description}
        </div>
      </div>
    ),
    { width: 1200, height: 630 }
  );
}

Add to app/layout.tsx — read the actual project name and description from package.json, README, or landing page copy:

export const metadata: Metadata = {
  metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL || 'https://yourdomain.com'),
  openGraph: {
    title: '[Project Name]',
    description: '[One-line description]',
    images: [{
      url: '/api/og?title=[Project Name]&description=[Description]',
      width: 1200,
      height: 630,
    }],
    type: 'website',
  },
  twitter: {
    card: 'summary_large_image',
    title: '[Project Name]',
    description: '[One-line description]',
    images: ['/api/og?title=[Project Name]&description=[Description]'],
  },
};

For dynamic pages (blog posts, skill pages, product pages), override per page:

// app/[slug]/page.tsx
export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}): Promise<Metadata> {
  const { slug } = await params;
  const item = getItem(slug);
  if (!item) return {};

  const ogUrl = `/api/og?title=${encodeURIComponent(item.title)}&description=${encodeURIComponent(item.description)}`;

  return {
    title: `${item.title} — [Site Name]`,
    description: item.description,
    openGraph: {
      images: [{ url: ogUrl, width: 1200, height: 630 }],
    },
    twitter: {
      images: [ogUrl],
    },
  };
}
Next.js Pages Router

Create pages/api/og.tsx with the same ImageResponse logic. Add meta tags via next/head in _app.tsx or per-page with <Head>.

Astro

Install: npm install satori @resvg/resvg-js

Create src/pages/og/[...slug].png.ts as an endpoint that uses satori to generate a PNG buffer and returns it with Content-Type: image/png.

Add meta tags in src/layouts/Layout.astro in the <head> section.

Static HTML

Generate a static og-image.png (1200×630) once, and reference it:

<meta property="og:image" content="https://yourdomain.com/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

For static sites with many pages, consider generating per-page OG images at build time.

Phase 3: Required Meta Tags

Every page needs these. Inject them in the base layout:

<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="[Canonical page URL]" />
<meta property="og:title" content="[Page title]" />
<meta property="og:description" content="[Page description — 1-2 sentences]" />
<meta property="og:image" content="[OG image URL — absolute, not relative]" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

<!-- Twitter / X -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="[Page title]" />
<meta name="twitter:description" content="[Page description]" />
<meta name="twitter:image" content="[OG image URL — must match og:image]" />

Common mistakes that break previews:

  • Using relative URLs for og:image — must be absolute (https://...)
  • Setting og:image but not twitter:image — Twitter ignores og:image
  • Missing og:image:width and :height — causes slow rendering and sometimes no preview
  • Not setting metadataBase in Next.js — all relative URLs become broken

Phase 4: Design Rules

The image renders at 1200×630 on desktop and gets thumbnail-cropped on mobile. Design for both:

  • Text must be readable at 300px wide — that's how it looks in a Slack/Twitter feed
  • Keep all content within center 80% — platforms crop the edges unpredictably
  • Dark background preferred — stands out in light-mode feeds
  • Title: 48-64px bold — readable at thumbnail size
  • Description/subtitle: 24-32px, lower opacity — supporting context
  • Minimum 4.5:1 contrast ratio — both light and dark mode platforms

Phase 5: Verify

Paste your URL into these tools after deploying. Don't skip this — meta tags look correct in code but break in practice more often than you'd expect:

  • Twitter Card Validator: cards-dev.twitter.com/validator
  • LinkedIn Post Inspector: linkedin.com/post-inspector/
  • Facebook Debugger: developers.facebook.com/tools/debug/ (also works for WhatsApp)
  • Quick check: paste URL in any Slack channel
[ ] OG image route returns valid image at /api/og (or equivalent)
[ ] og:title, og:description, og:image all set in base layout
[ ] og:image:width and og:image:height set
[ ] twitter:card set to summary_large_image
[ ] twitter:image set (separate from og:image — both required)
[ ] og:image URL is absolute, not relative
[ ] Dynamic pages have per-page og:image with correct title param
[ ] Image readable at 300px thumbnail width
[ ] Verified in Twitter Card Validator or LinkedIn Inspector

Comments

Markdown supported