TutorialNext.jsReactWordPress

How to Add Open Graph Tags to Next.js, React, and WordPress

·12 min read·By BrandSnap Team

You've created a great OG image. Now you need to actually add the Open Graph meta tags so platforms know about it. The implementation varies significantly between Next.js (App Router), React (SPAs with react-helmet), and WordPress — and getting it wrong means your images won't show up when shared.

This tutorial covers the exact code for each framework with copy-paste examples, common pitfalls, and how to use BrandSnap to generate the images themselves. Let's get your links looking professional on every platform.

Quick Reference: Essential OG Tags

Before diving into framework-specific implementations, here are the meta tags you need on every page:

<!-- Required Open Graph Tags -->
<meta property="og:title" content="Page Title" />
<meta property="og:description" content="Page description (150-160 chars)" />
<meta property="og:image" content="https://yoursite.com/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Description of the image" />
<meta property="og:url" content="https://yoursite.com/page" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Your Site Name" />

<!-- Twitter Card Tags -->
<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="https://yoursite.com/og-image.png" />
<meta name="twitter:image:alt" content="Description of the image" />

⚠️ Critical rule: The og:image URL must be absolute (starting with https://) and served over HTTPS. Relative paths like /images/og.png will not work on any platform.

Next.js App Router (Metadata API)

Next.js 13+ App Router has a built-in Metadata API that's the cleanest way to add OG tags. Export a metadata object from any page.tsx or layout.tsx:

app/blog/my-post/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My Blog Post Title',
  description: 'A compelling description of your blog post.',
  openGraph: {
    title: 'My Blog Post Title',
    description: 'A compelling description of your blog post.',
    url: 'https://yoursite.com/blog/my-post',
    type: 'article',
    publishedTime: '2026-06-15T00:00:00Z',
    authors: ['Your Name'],
    images: [
      {
        url: 'https://yoursite.com/og/my-post.png',
        width: 1200,
        height: 630,
        alt: 'My Blog Post Title',
      },
    ],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'My Blog Post Title',
    description: 'A compelling description of your blog post.',
    images: ['https://yoursite.com/og/my-post.png'],
  },
}

export default function MyPost() {
  return (
    <article>
      <h1>My Blog Post Title</h1>
      {/* ... */}
    </article>
  )
}

Next.js automatically renders these as the correct <meta> tags in the page's <head>. No need to manually write HTML meta tags.

Setting Global Defaults in layout.tsx

Set site-wide defaults in your root layout. Page-level metadata will override these:

app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  metadataBase: new URL('https://yoursite.com'),
  title: {
    default: 'Your Site Name',
    template: '%s | Your Site Name',
  },
  description: 'Your site description.',
  openGraph: {
    type: 'website',
    locale: 'en_US',
    url: 'https://yoursite.com',
    siteName: 'Your Site Name',
    images: [
      {
        url: '/og-default.png', // metadataBase makes this absolute
        width: 1200,
        height: 630,
        alt: 'Your Site Name',
      },
    ],
  },
  twitter: {
    card: 'summary_large_image',
    site: '@youraccount',
  },
}

💡 Pro tip: Setting metadataBase in your root layout lets you use relative paths for images in child pages. Next.js will automatically resolve them to absolute URLs.

Next.js: Dynamic OG Images with generateMetadata

For dynamic pages (blog posts from a CMS, product pages, etc.), use the generateMetadata function to fetch data and generate OG tags dynamically:

app/blog/[slug]/page.tsx
import type { Metadata } from 'next'

type Props = {
  params: Promise<{ slug: string }>
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const post = await getPostBySlug(slug) // Your data fetching

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      url: `https://yoursite.com/blog/${slug}`,
      type: 'article',
      publishedTime: post.publishedAt,
      images: [
        {
          url: post.ogImage, // From your CMS or BrandSnap
          width: 1200,
          height: 630,
          alt: post.title,
        },
      ],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.ogImage],
    },
  }
}

export default async function BlogPost({ params }: Props) {
  const { slug } = await params
  const post = await getPostBySlug(slug)

  return (
    <article>
      <h1>{post.title}</h1>
      {/* ... */}
    </article>
  )
}

Adding JSON-LD Structured Data

For maximum SEO benefit, pair OG tags with JSON-LD structured data. Add it as a script tag in your component:

const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'Article',
  headline: post.title,
  description: post.excerpt,
  image: post.ogImage,
  datePublished: post.publishedAt,
  author: {
    '@type': 'Person',
    name: post.author,
  },
}

// In your component's JSX:
<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>

React SPA with react-helmet-async

For React single-page applications (Create React App, Vite, etc.), use react-helmet-async to manage meta tags in the document head:

Terminal
npm install react-helmet-async

Step 1: Wrap Your App with HelmetProvider

src/main.tsx (or index.tsx)
import { HelmetProvider } from 'react-helmet-async'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <HelmetProvider>
    <App />
  </HelmetProvider>
)

Step 2: Create a Reusable SEO Component

src/components/SEO.tsx
import { Helmet } from 'react-helmet-async'

interface SEOProps {
  title: string
  description: string
  image: string
  url: string
  type?: string
  publishedTime?: string
  author?: string
}

export function SEO({
  title,
  description,
  image,
  url,
  type = 'website',
  publishedTime,
  author,
}: SEOProps) {
  return (
    <Helmet>
      {/* Primary Meta Tags */}
      <title>{title}</title>
      <meta name="description" content={description} />

      {/* Open Graph */}
      <meta property="og:type" content={type} />
      <meta property="og:url" content={url} />
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={image} />
      <meta property="og:image:width" content="1200" />
      <meta property="og:image:height" content="630" />
      <meta property="og:image:alt" content={title} />
      <meta property="og:site_name" content="Your Site Name" />
      {publishedTime && (
        <meta property="article:published_time" content={publishedTime} />
      )}
      {author && (
        <meta property="article:author" content={author} />
      )}

      {/* Twitter Card */}
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:url" content={url} />
      <meta name="twitter:title" content={title} />
      <meta name="twitter:description" content={description} />
      <meta name="twitter:image" content={image} />
      <meta name="twitter:image:alt" content={title} />
    </Helmet>
  )
}

Step 3: Use It in Your Pages

src/pages/BlogPost.tsx
import { SEO } from '../components/SEO'

export function BlogPost({ post }) {
  return (
    <>
      <SEO
        title={post.title}
        description={post.excerpt}
        image={`https://yoursite.com/og/${post.slug}.png`}
        url={`https://yoursite.com/blog/${post.slug}`}
        type="article"
        publishedTime={post.publishedAt}
        author={post.author}
      />
      <article>
        <h1>{post.title}</h1>
        {/* ... */}
      </article>
    </>
  )
}

React SSR Considerations

⚠️ Important: SPAs and Social Crawlers

Here's the catch with React SPAs: most social media crawlers (Facebook, Twitter, LinkedIn) do not execute JavaScript. They fetch the raw HTML and read the meta tags directly. If your OG tags are only rendered client-side via react-helmet, crawlers won't see them.

Solutions:

  • 1.Server-Side Rendering (SSR): Use Next.js, Remix, or a custom SSR setup so meta tags are in the initial HTML response.
  • 2.Pre-rendering: Use tools like react-snap or prerender.io to generate static HTML snapshots that crawlers can read.
  • 3.Static Site Generation (SSG): If your content is static, use a framework with SSG support (Next.js, Gatsby, Astro) to pre-render pages with OG tags baked in.

Our recommendation: If you're starting a new React project and care about social sharing, use Next.js with the App Router. The metadata API handles SSR automatically, and you never have to worry about crawlers missing your OG tags.

WordPress: Plugin Approach (Yoast / Rank Math)

The easiest way to add OG tags to WordPress is with an SEO plugin. Here's how to set it up with the two most popular options:

Yoast SEO

1

Install and activate Yoast SEO from Plugins → Add New.

2

Go to Yoast SEO → Social in the admin sidebar. Enable the Open Graph meta data checkbox under the Facebook tab.

3

Edit any post or page. Scroll to the Yoast SEO meta box, click the "Social" tab, and upload your OG image for that specific page.

4

Set a default OG image under Yoast → Social → Facebook tab for pages that don't have a specific image.

Rank Math

1

Install and activate Rank Math. Run the setup wizard.

2

Go to Rank Math → General Settings → Social. Configure default OG image and other social settings.

3

On any post/page editor, click the Rank Math icon in the sidebar, go to "Social" tab, and set the OG image for that specific page.

💡 Tip: Both Yoast and Rank Math let you set different images for Facebook/OG and Twitter. Use this if you want to optimize image cropping for each platform. Generate both versions with BrandSnap's OG generator.

WordPress: Manual Implementation

If you prefer not to use a plugin (for performance or simplicity), add OG tags directly in your theme's functions.php:

functions.php
function add_og_meta_tags() {
    if (is_single() || is_page()) {
        global $post;
        $title = get_the_title();
        $description = has_excerpt()
            ? get_the_excerpt()
            : wp_trim_words(get_the_content(), 30);
        $url = get_permalink();
        $site_name = get_bloginfo('name');

        // Get OG image: featured image or fallback
        if (has_post_thumbnail()) {
            $image_id = get_post_thumbnail_id();
            $image = wp_get_attachment_image_src($image_id, 'full');
            $image_url = $image[0];
            $image_width = $image[1];
            $image_height = $image[2];
        } else {
            // Fallback to a default OG image
            $image_url = get_template_directory_uri() . '/og-default.png';
            $image_width = 1200;
            $image_height = 630;
        }

        echo '<meta property="og:title" content="' . esc_attr($title) . '" />';
        echo '<meta property="og:description" content="' . esc_attr($description) . '" />';
        echo '<meta property="og:image" content="' . esc_url($image_url) . '" />';
        echo '<meta property="og:image:width" content="' . esc_attr($image_width) . '" />';
        echo '<meta property="og:image:height" content="' . esc_attr($image_height) . '" />';
        echo '<meta property="og:url" content="' . esc_url($url) . '" />';
        echo '<meta property="og:type" content="article" />';
        echo '<meta property="og:site_name" content="' . esc_attr($site_name) . '" />';

        // Twitter Card
        echo '<meta name="twitter:card" content="summary_large_image" />';
        echo '<meta name="twitter:title" content="' . esc_attr($title) . '" />';
        echo '<meta name="twitter:description" content="' . esc_attr($description) . '" />';
        echo '<meta name="twitter:image" content="' . esc_url($image_url) . '" />';
    }
}
add_action('wp_head', 'add_og_meta_tags');

This approach uses the post's featured image as the OG image. Upload your BrandSnap-generated images as featured images, and they'll automatically be used as OG images.

Using Custom Fields for More Control

For a separate OG image field (distinct from your featured image), add a custom meta box:

functions.php (additional)
// In the og_meta_tags function, replace the image logic:
$custom_og = get_post_meta($post->ID, 'og_image_url', true);
if ($custom_og) {
    $image_url = $custom_og;
    $image_width = 1200;
    $image_height = 630;
} elseif (has_post_thumbnail()) {
    // ... featured image fallback
}

Generating the OG Images with BrandSnap

Now that you know how to implement the tags, you need the actual images. BrandSnap generates professional OG images in seconds — here's how it fits into each workflow:

For Next.js Projects

  1. 1.Generate your OG image at www.brandsnap.io/tools/og-image-generator
  2. 2.Download the PNG and place it in your public/og/ directory
  3. 3.Reference it in your metadata: images: [{url: '/og/my-post.png'}]
  4. 4.metadataBase automatically makes it an absolute URL

For React SPAs

  1. 1.Generate images with BrandSnap for each key page
  2. 2.Host them on your CDN or in your public/ folder
  3. 3.Pass the full URL to your SEO component
  4. 4.Remember: use SSR or pre-rendering so crawlers can see the tags

For WordPress

  1. 1.Generate images with BrandSnap for each post/page
  2. 2.Upload as the featured image (or to the Yoast/Rank Math OG image field)
  3. 3.Your SEO plugin or custom code handles the rest

For Teams with BrandSnap API (Agency Plan)

Automate the entire process: generate OG images as part of your build or CMS workflow.

// In your build script or CMS webhook:
async function generateOGImage(pageUrl: string, slug: string) {
  const res = await fetch('https://api.brandsnap.io/v1/generate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.BRANDSNAP_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: pageUrl,
      type: 'og-image',
      style: 'neo-brutal',
    }),
  })

  const { imageUrl } = await res.json()

  // Download and save to your public directory
  const image = await fetch(imageUrl)
  const buffer = await image.arrayBuffer()
  await fs.writeFile(`public/og/${slug}.png`, Buffer.from(buffer))
}

Testing Your Implementation

After adding OG tags, always validate before going live. Here's the testing checklist:

1. View Page Source

Right-click your deployed page → View Page Source. Search for og:image. The URL should be absolute, start with https://, and the image should load when you paste it in a browser.

2. Use Platform Validators

Test with the Facebook Debugger, Twitter Card Validator, and LinkedIn Post Inspector.

3. Check the Image Loads

Copy the og:image URL and open it directly in your browser. It should load quickly (under 3 seconds) and display the correct image. Check that the server returns the right Content-Type header (image/png or image/jpeg).

4. Test on Actual Platforms

Send your URL to yourself in Slack, Discord, or Twitter DMs. The preview should show your custom image, title, and description. If it shows a cached old version, use the platform's debugger to force a refresh.

Common Mistakes & Troubleshooting

Image not showing on social media

Cause: Relative URL, HTTP instead of HTTPS, or slow server response. Fix: Use absolute HTTPS URLs. Ensure image loads in <5 seconds. Clear platform cache with their debugger tool.

Old image showing after update

Cause: Platform caching. Social platforms cache OG images aggressively. Fix: Use the Facebook Debugger / LinkedIn Post Inspector to force re-scrape. Alternatively, change the image URL (add a query param like ?v=2).

Image looks cropped or wrong aspect ratio

Cause: Image isn't 1200×630 or critical content is near edges. Fix: Use exactly 1200×630 pixels. Keep important content in the center 80%. Include og:image:width and og:image:height tags.

React SPA: tags not visible to crawlers

Cause: Client-side-only rendering. Crawlers don't execute JavaScript. Fix: Use Next.js (SSR), pre-render with react-snap, or a pre-rendering service like Prerender.io.

WordPress: conflicting OG tags

Cause: Multiple plugins or themes outputting OG tags. Fix: Check View Source for duplicate og:image tags. Disable OG output in your theme if using Yoast/Rank Math, or vice versa.

Need the OG Images? Generate Them in Seconds

You now know how to implement the tags. Let BrandSnap handle the images — professional, brand-matched, and perfectly sized for every platform.