Blogs/Open Graph React SEO: Fix Social Previews and Add OG Meta Tags (2026 Guide)
Power-SEO

Open Graph React SEO: Fix Social Previews and Add OG Meta Tags (2026 Guide)

WhatsApp Image 2025-09-14 at 12.31.40

Mitu Das

super admin

May 14, 2026
Open Graph React SEO Guide: Add OG Meta Tags That Actually Work

Someone on your team just shared your React app link on LinkedIn. Blank card. No image. No title. Just the raw URL sitting there looking sad. You added the meta tags. You tested in the browser. Everything looked right. So why is LinkedIn showing nothing? This is one of the most common Open Graph React SEO problems developers run into, and the fix is simpler than you think, once you understand one key concept about how social media scrapers actually work.

This guide gives you working code for every major React setup: Next.js App Router, Next.js Pages Router, Remix, and Vite. It also explains data-react-helmet, why it trips people up, and what to use instead in 2026.

What you will learn:

  • Why social platforms show blank cards for React apps (the real reason)
  • How to add Open Graph meta tags that actually work, per framework
  • The data-react-helmet attribute explained clearly
  • OG image rules that platforms actually enforce
  • How to test and validate your Open Graph React SEO setup before going live

What Are Open Graph Meta Tags

Open Graph (OG) is a protocol Facebook launched in 2010. It is now the standard for Open Graph React SEO, used by Facebook, LinkedIn, WhatsApp, Slack, Discord, iMessage, and most other platforms that show link previews.

When someone shares your URL, the platform sends an HTTP request to your page. It reads the raw HTML and looks for <meta property="og:*"> tags inside your <head>. Those tags tell the platform what title, description, image, and URL to show in the preview card.

The four tags every page must have:

<meta property="og:title" content="Your Page Title" />
<meta property="og:description" content="A short description of this page." />
<meta property="og:image" content="https://example.com/og-image.jpg" />
<meta property="og:url" content="https://example.com/your-page" />

Add two more for best results:

<meta property="og:type" content="website" />
<meta property="og:site_name" content="Your Brand Name" />

Without these, the platform guesses. It usually guesses wrong, pulling a random image from your page, using your domain as the title, or showing nothing at all.

Why Your React App Shows Blank Social Previews

This is the question at the heart of every Open Graph React SEO problem, and most guides bury the answer in footnotes.

Social media scrapers do not run JavaScript.

Facebook's crawler (facebookexternalhit), LinkedIn's bot (LinkedInBot), and Twitter's scraper (Twitterbot) all send a plain HTTP GET request to your URL. They read the raw HTML the server sends back. Then they leave. They do not wait for JavaScript to load and execute.

If your React app is a classic Single Page Application, built with Create React App or plain Vite, the server sends back something like this:

<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>

There are no Open Graph tags in that HTML. Your JavaScript runs in the browser and injects them later, but the scraper is already gone. It never saw them.

That is why your previews are blank. Not because you wrote the tags wrong. Because the tags arrive too late for the scraper to read them.

The fix is server-side rendering. For proper Open Graph React SEO, your OG tags need to be in the HTML the server sends on the very first request, before any JavaScript runs.

What Is data-react-helmet and Should You Worry About It

If you have used React Helmet, you have seen this attribute in your HTML:

<meta property="og:title" content="My Page" data-react-helmet="true" />

Every meta tag React Helmet manages gets tagged with data-react-helmet="true". It is an internal bookkeeping attribute. React Helmet uses it to track which tags it controls so it can remove or update them when components unmount or re-render.

Does data-react-helmet hurt Open Graph React SEO? No. Google ignores it completely. It has zero effect on your search rankings.

But here is the real problem. React Helmet is a client-side library. It injects tags into document.head after JavaScript runs in the browser. Social media scrapers do not run JavaScript. So unless your React app is server-side rendered, every Open Graph tag React Helmet adds is invisible to social platforms.

The data-react-helmet attribute is not the bug. The client-side-only rendering is the bug.

Also worth knowing: the original react-helmet package is unmaintained since 2022. The community fork react-helmet-async is the actively maintained replacement if you need a Helmet-style API for a React SPA.

How to Add Open Graph Meta Tags in React

The right approach to Open Graph React SEO depends entirely on which framework you are using. Here is the working solution for each one.

Next.js App Router (Next.js 13+)

Next.js App Router has a built-in Metadata API. It renders meta tags on the server, which means scrapers see them on the first request. You do not need React Helmet.

Static page:

// app/page.tsx
import { createMetadata } from '@power-seo/meta';

export const metadata = createMetadata({
  title: 'Home',
  description: 'Welcome to Example Site.',
  canonical: 'https://example.com/',
  openGraph: {
    type: 'website',
    siteName: 'Example Site',
    images: [
      {
        url: 'https://example.com/og-home.jpg',
        width: 1200,
        height: 630,
        alt: 'Example Site homepage',
      },
    ],
  },
  twitter: { card: 'summary_large_image', site: '@examplesite' },
  robots: { index: true, follow: true },
});

export default function HomePage() {
  return <main>{/* page content */}</main>;
}

createMetadata() from @power-seo/meta accepts a single SeoConfig object and returns a native Next.js App Router Metadata object. It handles all robots directives, Open Graph images, and Twitter Card fields from one config.

Dynamic page (blog post, product, profile):

// app/blog/[slug]/page.tsx
import { createMetadata } from '@power-seo/meta';
import { getPost } from '@/lib/posts';

export async function generateMetadata({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);

  if (!post) return { title: 'Post Not Found' };

  return createMetadata({
    title: post.title,
    description: post.excerpt,
    canonical: `https://example.com/blog/${params.slug}`,
    openGraph: {
      type: 'article',
      images: [{ url: post.coverImage, width: 1200, height: 630, alt: post.title }],
      article: {
        publishedTime: post.publishedAt,
        modifiedTime: post.updatedAt,
        authors: [post.author.profileUrl],
        tags: post.tags,
      },
    },
    robots: { index: !post.isDraft, follow: true, maxSnippet: 160, maxImagePreview: 'large' },
  });
}

export default async function BlogPostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  return <article>{/* render post */}</article>;
}

Site-wide defaults in your root layout:

// app/layout.tsx
import { createMetadata } from '@power-seo/meta';

export const metadata = createMetadata({
  title: {
    default: 'Example Site',
    template: '%s | Example Site',
  },
  description: 'Learn React SEO, structured data, and performance.',
  openGraph: {
    type: 'website',
    siteName: 'Example Site',
    images: [{ url: 'https://example.com/og-default.jpg', width: 1200, height: 630 }],
  },
  twitter: { card: 'summary_large_image', site: '@examplesite' },
});

Set defaults once in the layout. Every child page that sets its own title automatically gets "Page Title | Example Site" without repeating the site name everywhere.

One extra tip for Next.js: if you fetch the same data for both the metadata and the page, use React's cache() function to avoid making the same database query twice:

import { cache } from 'react';
import { db } from '@/app/lib/db';

export const getPost = cache(async (slug: string) => {
  return await db.query.posts.findFirst({ where: eq(posts.slug, slug) });
});

Next.js Pages Router

If you are on the older Pages Router, use next/head. It is server-rendered by default, so scrapers see your Open Graph React SEO tags immediately.

// pages/blog/[slug].tsx
import Head from 'next/head';
import { GetServerSideProps } from 'next';

interface Post {
  title: string;
  excerpt: string;
  coverImage: string;
  slug: string;
}

export default function BlogPost({ post }: { post: Post }) {
  const pageUrl = `https://example.com/blog/${post.slug}`;

  return (
    <>
      <Head>
        <title>{`${post.title} | Example Site`}</title>
        <meta name="description" content={post.excerpt} />

        {/* Open Graph */}
        <meta property="og:type" content="article" />
        <meta property="og:title" content={post.title} />
        <meta property="og:description" content={post.excerpt} />
        <meta property="og:image" content={post.coverImage} />
        <meta property="og:url" content={pageUrl} />
        <meta property="og:site_name" content="Example Site" />

        {/* Twitter Card */}
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:title" content={post.title} />
        <meta name="twitter:description" content={post.excerpt} />
        <meta name="twitter:image" content={post.coverImage} />

        {/* Canonical, do not skip this */}
        <link rel="canonical" href={pageUrl} />
      </Head>
      <article>{/* render post content */}</article>
    </>
  );
}

export const getServerSideProps: GetServerSideProps = async ({ params }) => {
  const post = await getPost(params?.slug as string);
  return { props: { post } };
};

One thing that gets skipped constantly: the canonical tag. Without it, Google may index your page under multiple URLs (/blog/post, /blog/post/, ?ref=social) and split your link equity across duplicates. Always include it.

Remix v2

Remix uses an exported meta function per route. Server-rendered by default, so scrapers get your Open Graph tags on the first request.

// app/routes/blog.$slug.tsx
import { createMetaDescriptors } from '@power-seo/meta';
import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { getPost } from '~/lib/posts.server';

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await getPost(params.slug!);
  if (!post) throw new Response('Not Found', { status: 404 });
  return json({ post });
}

export const meta: MetaFunction<typeof loader> = ({ data }) => {
  if (!data?.post) return [{ title: 'Post Not Found' }];

  return createMetaDescriptors({
    title: data.post.title,
    description: data.post.excerpt,
    canonical: `https://example.com/blog/${data.post.slug}`,
    openGraph: {
      type: 'article',
      images: [{ url: data.post.coverImage, width: 1200, height: 630 }],
    },
    twitter: { card: 'summary_large_image' },
  });
};

export default function BlogPost() {
  return <article>{/* render post */}</article>;
}

createMetaDescriptors() from @power-seo/meta maps the same SeoConfig to a native Remix MetaDescriptor[] array. No manual tag building.

Vite and Client-Side React

This is the trickiest Open Graph React SEO scenario. Pure Vite or Create React App projects render everything in the browser, so scrapers see an empty shell.

You have three options:

Option 1: Use React 19 native head hoisting with @power-seo/react (best for React 19+)

React 19 lets you put <title>, <meta>, and <link> tags directly in your components and they hoist automatically to <head>. With a Node.js server doing SSR, this gives scrapers proper Open Graph tags on the first request.

// App.jsx
import { DefaultSEO, SEO } from '@power-seo/react';

function App() {
  return (
    <DefaultSEO
      titleTemplate="%s | My Site"
      defaultTitle="My Site"
      description="A great site built with React."
      openGraph={{
        type: 'website',
        siteName: 'My Site',
        images: [{ url: 'https://example.com/og-default.jpg', width: 1200, height: 630 }],
      }}
      twitter={{ site: '@mysite', cardType: 'summary_large_image' }}
    >
      <Router>
        <Routes />
      </Router>
    </DefaultSEO>
  );
}

// pages/BlogPage.jsx
function BlogPage({ post }) {
  return (
    <>
      <SEO
        title={post.title}
        description={post.excerpt}
        canonical={`https://example.com/blog/${post.slug}`}
        openGraph={{
          type: 'article',
          images: [{ url: post.coverImage, width: 1200, height: 630, alt: post.title }],
        }}
      />
      <article>{/* content */}</article>
    </>
  );
}

<DefaultSEO> sets site-wide defaults using React context. Each <SEO> component on individual pages merges its own values over those defaults. You get title templates, fallback images, and global robots directives with zero repetition.

Option 2: Pre-render with Vite SSG or move to Next.js (best for SEO)

If organic search and social sharing drive your traffic, pre-render your pages. Every major Vite SSG plugin and Astro will generate static HTML files with your Open Graph React SEO tags already in place, so no JavaScript is needed for crawlers to see them.

Option 3: react-helmet-async (fallback for React 17/18)

If you cannot upgrade to React 19 or add SSR right now, react-helmet-async is the maintained fork of React Helmet. The data-react-helmet behavior is the same. The client-side limitation is the same. But it is actively maintained and works with concurrent mode.

Use this only as a stopgap. It does not solve the scraper visibility problem for pure SPAs.

Open Graph Image Rules That Platforms Actually Enforce

Getting your OG image right matters more than any other part of your Open Graph React SEO setup. A compelling image drives 2-3x more clicks than a text-only preview card.

Here are the rules that actually matter:

Size: 1200x630 pixels. This is the 1.91:1 ratio Facebook and LinkedIn prefer. Twitter also accepts it. Minimum is 200x200 pixels, but anything smaller than 600x315 looks bad on modern screens.

Format: JPEG or PNG. JPEG is the universal safe choice. PNG works well for graphics, screenshots, and text-heavy images. WebP is now accepted by most platforms but JPEG remains the safe default.

File size: under 1MB. Facebook has a hard limit. Large images simply do not load in preview cards.

URL: absolute HTTPS only. Write https://example.com/og-image.jpg, not /og-image.jpg. Scrapers cannot resolve relative paths. HTTP (not HTTPS) gets blocked on some platforms entirely.

Unique per page. Platforms cache OG images aggressively per URL. If every page uses the same image, every link share looks identical. Worse, if you update that image, scrapers may show the old cached version for weeks. Unique images per page fix this.

Here is how to validate OG image dimensions programmatically before they ship using @power-seo/preview:

import { generateOgPreview } from '@power-seo/preview';

const og = generateOgPreview({
  title: 'React SEO Guide',
  description: 'The complete guide to Open Graph in React.',
  url: 'https://example.com/react-seo',
  image: {
    url: 'https://example.com/og.jpg',
    width: 800,
    height: 400,
  },
});

if (og.image && !og.image.valid) {
  throw new Error(`OG image failed: ${og.image.message}`);
  // "Image is 800x400px. Minimum size is 200x200px."
}

generateOgPreview() returns an OgPreviewData object where og.image.valid is false if dimensions fall outside the recommended minimums, and og.image.message gives you a human-readable reason. Run this in your CI pipeline. Catch bad images before LinkedIn shows your link with a blank card.

You can also validate your SERP title and description truncation in the same pipeline:

import { generateSerpPreview } from '@power-seo/preview';

const serp = generateSerpPreview({
  title: post.title,
  description: post.excerpt,
  url: `https://example.com/blog/${post.slug}`,
  siteTitle: 'Example Site',
});

if (serp.titleTruncated) {
  console.warn(`Title too long for SERP: "${serp.title}"`);
}

Twitter/X Card Tags: Do Not Skip These

Open Graph and Twitter Cards are separate systems. Twitter reads OG tags as a fallback, but its own twitter:* tags take priority. A complete Open Graph React SEO setup requires both sets.

The minimum Twitter Card tags for a blog post or article:

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@yoursite" />
<meta name="twitter:title" content="Your Page Title" />
<meta name="twitter:description" content="Your page description." />
<meta name="twitter:image" content="https://example.com/twitter-image.jpg" />
<meta name="twitter:image:alt" content="Description of the image for accessibility" />

Use summary_large_image for blog posts and articles. It shows a large image above the link text, which gets dramatically more clicks than summary (small square thumbnail).

The twitter:image:alt tag is forgotten by most developers. Add it. It is required for accessibility compliance and checked by Twitter's card validator.

Using the <TwitterCard> component from @power-seo/react:

import { TwitterCard } from '@power-seo/react';

function PostHead({ post }) {
  return (
    <TwitterCard
      cardType="summary_large_image"
      site="@mysite"
      creator="@author_handle"
      title={post.title}
      description={post.excerpt}
      image={post.coverImage}
      imageAlt={`Cover image for: ${post.title}`}
    />
  );
}

Article-Type Open Graph for Blog Posts

When your page is a blog post or news article, set og:type to article and add the article namespace tags. These unlock richer previews on Facebook, including "Updated X days ago" badges, and help Google understand your content freshness, which is a meaningful Open Graph React SEO signal.

<meta property="og:type" content="article" />
<meta property="article:published_time" content="2026-05-14T09:00:00Z" />
<meta property="article:modified_time" content="2026-05-14T12:00:00Z" />
<meta property="article:author" content="https://example.com/author/jane-doe" />
<meta property="article:section" content="Technical SEO" />
<meta property="article:tag" content="react" />
<meta property="article:tag" content="open graph" />

In createMetadata() or createMetaDescriptors() from @power-seo/meta, the article namespace maps to the openGraph.article config field:

openGraph: {
  type: 'article',
  images: [{ url: post.coverImage, width: 1200, height: 630 }],
  article: {
    publishedTime: post.publishedAt,
    modifiedTime: post.updatedAt,
    authors: [post.author.profileUrl],
    tags: post.tags,
  },
},

The modifiedTime field is the one people skip. Without it, Facebook shows the original publish date. If you updated the article last week, readers may assume the content is old, even when it is fresh and accurate.

How to Validate Your Open Graph React SEO Tags

Never assume your tags are correct. Test them before and after every significant change.

Facebook Sharing Debugger → https://developers.facebook.com/tools/debug/

This is your most important Open Graph React SEO testing tool. Paste any URL, click "Debug," and see exactly what Facebook's crawler reads. If you updated your tags and Facebook still shows old data, click "Scrape Again" to force a cache refresh.

Twitter Card Validator → https://cards-dev.twitter.com/validator

Validates all Twitter Card tags and shows a live preview. Requires a Twitter login.

LinkedIn Post Inspector → https://www.linkedin.com/post-inspector/

LinkedIn has its own image size preferences and slightly different caching behavior. Worth checking separately from Facebook.

Automated check in CI using @power-seo/preview:

import { generateSerpPreview, generateOgPreview } from '@power-seo/preview';

const serp = generateSerpPreview({
  title: post.title,
  description: post.excerpt,
  url: `https://example.com/blog/${post.slug}`,
  siteTitle: 'Example Site',
});

if (serp.titleTruncated) {
  console.warn(`Title too long for SERP: "${serp.title}"`);
}

const og = generateOgPreview({
  title: post.title,
  description: post.excerpt,
  url: `https://example.com/blog/${post.slug}`,
  image: { url: post.coverImage, width: post.ogWidth, height: post.ogHeight },
});

if (og.image && !og.image.valid) {
  throw new Error(`OG image invalid: ${og.image.message}`);
}

console.log('All OG checks passed ✓');

Run this on every pull request. It takes milliseconds and catches Open Graph React SEO mistakes before they reach production.

The 8 Most Common Open Graph React SEO Mistakes

These appear in almost every codebase:

1. Relative image URL. Writing /og-image.jpg instead of https://example.com/og-image.jpg. Scrapers cannot resolve relative paths. Always use absolute URLs.

2. Missing og:url. Without it, platforms pick a URL from your page's links, rarely the canonical one you want. Set it explicitly on every page.

3. Same image on every page. Platform caches are per-URL. Use unique images or at minimum unique images per content type (articles, products, homepage).

4. No 404 fallback. If someone shares a broken URL, your error page renders without any OG tags. At minimum, add fallback OG tags to your 404 component.

5. OG tags in JavaScript only. If you are using React Helmet in a client-side SPA, social scrapers never see your tags. Move to a server-rendered approach.

6. Wrong og:type. Using website for blog posts instead of article. This means you miss out on article-specific preview features and freshness signals.

7. Missing og:site_name. Without it, platforms show only your domain. With it, they show your brand name. Takes 30 seconds to add.

8. Encoding errors in content. An apostrophe written as ' instead of &apos; or &#39; looks wrong in previews. Framework metadata APIs handle this automatically. Raw HTML strings do not, so escape them manually.

Solve Your Open Graph React SEO Today

Here is the short version of everything above.

If you are on Next.js App Router, use createMetadata() from @power-seo/meta in generateMetadata() or as a static metadata export. Tags are server-rendered. Scrapers see them. Done.

If you are on Next.js Pages Router, use next/head inside your page components with getServerSideProps or getStaticProps. Server-rendered. Done.

If you are on Remix, use createMetaDescriptors() from @power-seo/meta in your route's meta function. Server-rendered. Done.

If you are on Vite or Create React App with no SSR, you have an Open Graph React SEO problem that no meta tag library can fully solve. The right fix is to add SSR or pre-render your key pages. The quick workaround is react-helmet-async with the understanding that social scrapers will not see the tags.

Now go open your highest-traffic page. Paste its URL into the Facebook Sharing Debugger. Look at what Facebook actually sees.

If the preview is wrong or blank, you now know exactly why and exactly how to fix it.

Add the four essential tags, og:title, og:description, og:image, og:url, to one page today. Make them server-rendered. Make the image 1200x630 pixels. Make the URL absolute.

Then move to the next page.

Frequently Asked Questions 

What is the difference between Open Graph tags and meta tags?

Regular meta tags (<meta name="description"> and <title>) are designed for search engines and control how your page appears in Google search results. Open Graph tags (<meta property="og:*">) are designed for social platforms and control how your page appears when someone shares a link on Facebook, LinkedIn, Slack, or WhatsApp. A complete Open Graph React SEO setup requires both. One does not replace the other.

How do I add Open Graph meta tags in React without Next.js?

In React 19, use <DefaultSEO> and <SEO> from @power-seo/react. Tags hoist to <head> natively. In React 17/18, use react-helmet-async. In both cases, your Open Graph SEO tags only work reliably for social scrapers if your app is server-side rendered. A client-only SPA cannot show OG tags to social scrapers regardless of which library you use.

Why does data-react-helmet appear on my meta tags?

data-react-helmet="true" is added by the React Helmet library to track which <head> tags it manages. The attribute is harmless; Google and social platforms ignore it. The real Open Graph React SEO problem it signals is that React Helmet is managing your tags on the client side, which means social scrapers may not see them.

Why are my Open Graph tags not showing on Facebook?

Two common causes. First, Facebook caches OG data aggressively per URL. Use the Facebook Sharing Debugger at developers.facebook.com/tools/debug and click "Scrape Again" to force a fresh fetch. Second, and more common for React apps, your OG tags are injected by JavaScript after page load. Facebook's crawler does not run JavaScript and never sees them. Move your tags to server-rendered HTML.

What is the best OG image size for 2026?

The recommended size is 1200x630 pixels (1.91:1 ratio). Use HTTPS, keep the file under 1MB, and use an absolute URL in the og:image tag. Make the image unique per page if possible. LinkedIn and Facebook both display this size cleanly. Twitter's preferred ratio for summary_large_image is 2:1, so 1200x600 is technically ideal for Twitter, but 1200x630 works on all platforms. Use generateOgPreview() from @power-seo/preview to validate dimensions before they ship.

Does Open Graph SEO affect Google search rankings?

Open Graph tags are not a direct Google ranking factor. But the indirect effect is real. Pages with compelling social previews get shared more, which generates backlinks, which does affect rankings. A higher click-through rate from social shares compounds over time into meaningful traffic and authority growth. In 2026, AI-driven discovery systems also use page metadata, including OG tags, when summarizing and citing content. Clean Open Graph React SEO helps your content get surfaced and cited by AI tools.

Code copied to clipboard

FAQ

Frequently Asked Questions

We offer end-to-end digital solutions including website design & development, UI/UX design, SEO, custom ERP systems, graphics & brand identity, and digital marketing.

Timelines vary by project scope. A standard website typically takes 3-6 weeks, while complex ERP or web application projects may take 2-5 months.

Yes - we offer ongoing support and maintenance packages for all projects. Our team is available to handle updates, bug fixes, performance monitoring, and feature additions.

Absolutely. Visit our Works section to browse our portfolio of completed projects across various industries and service categories.

Simply reach out via our contact form or call us directly. We will schedule a free consultation to understand your needs and provide a tailored proposal.