DEV Community

Mitu Das
Mitu Das

Posted on • Originally published at ccbd.dev

Open Graph React SEO: Why React App Looks Broken on Twitter And How to Fix It

I spent nearly 3 hours debugging why my React app looked perfect in the browser but turned into a blank card when shared on Twitter.

No image. No title. No description.

Just a sad naked URL.

The frustrating part was that my meta tags were technically correct. The real issue was simpler: most social crawlers do not execute client-side React the same way browsers do.

In this article, you’ll learn how to fix Open Graph React SEO properly using server-rendered meta tags, generate dynamic OG images, and avoid the most common SEO mistakes in React apps without rebuilding your entire stack.

The React SEO Problem Nobody Notices Until Production

If you’ve built a React SPA using Vite, CRA, or React Router, your index.html probably looks something like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>My React App</title>
    <meta name="description" content="Cool app" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Then somewhere inside React:

import { Helmet } from "react-helmet";

export default function BlogPost() {
  return (
    <>
      <Helmet>
        <title>React SEO Guide</title>

        <meta
          property="og:title"
          content="React SEO Guide"
        />

        <meta
          property="og:description"
          content="How to fix Open Graph tags in React"
        />
      </Helmet>

      <h1>Blog Post</h1>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Looks correct, right?

But many crawlers fetch the initial HTML before React hydrates.

That means they only see:

<title>My React App</title>
Enter fullscreen mode Exit fullscreen mode

…and completely miss your dynamic Open Graph tags.

Result:

  • Broken social previews
  • Wrong titles on Discord, Twitter, and LinkedIn
  • Poor indexing
  • Lower click-through rates

This is one of the most common Open Graph React SEO issues developers run into.

The Fastest Fix Is Injecting OG Tags Server Side

You do not need to migrate your app to Next.js.

You just need the server to return fully rendered meta tags.

Here’s a lightweight Express setup that works with existing React apps.

Step 1: Create an Express server

Install Express:

npm install express
Enter fullscreen mode Exit fullscreen mode

Create server.js:

import express from "express";
import fs from "fs";
import path from "path";

const app = express();

app.use(express.static("dist"));

app.get("/post/:slug", (req, res) => {
  const slug = req.params.slug;

  const post = {
    title: "Fix React Open Graph Tags",
    description: "Dynamic OG tags for React apps",
    image: "https://mysite.com/og-image.png"
  };

  let html = fs.readFileSync(
    path.resolve("dist/index.html"),
    "utf8"
  );

  html = html
    .replace("__TITLE__", post.title)
    .replace("__DESCRIPTION__", post.description)
    .replace("__IMAGE__", post.image);

  res.send(html);
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Step 2: Add placeholders to index.html

<head>
  <title>__TITLE__</title>

  <meta
    property="og:title"
    content="__TITLE__"
  />

  <meta
    property="og:description"
    content="__DESCRIPTION__"
  />

  <meta
    property="og:image"
    content="__IMAGE__"
  />
</head>
Enter fullscreen mode Exit fullscreen mode

Now crawlers receive the correct tags immediately.

No hydration required.

Result

When someone shares:

https://mysite.com/post/react-seo-guide
Enter fullscreen mode Exit fullscreen mode

Twitter, Discord, LinkedIn, and Facebook now render:

  • Correct title
  • Correct description
  • Preview image

This is the core fix behind most Open Graph React SEO problems.

Dynamic OG Images Improve CTR More Than Most Developers Expect

Most developers stop after adding titles and descriptions.

But preview images dramatically improve click-through rates.

Static images work, but dynamic images are much better for blogs, changelogs, dashboards, and documentation pages.

Here’s a simple dynamic OG image endpoint using React and Satori.

Install dependencies:

npm install satori sharp
Enter fullscreen mode Exit fullscreen mode

Create og.js:

import satori from "satori";
import sharp from "sharp";

export async function generateOgImage(title) {
  const svg = await satori(
    {
      type: "div",
      props: {
        style: {
          display: "flex",
          width: "1200px",
          height: "630px",
          background: "#111",
          color: "#fff",
          alignItems: "center",
          justifyContent: "center",
          fontSize: "64px",
          padding: "40px"
        },
        children: title
      }
    },
    {
      width: 1200,
      height: 630
    }
  );

  return sharp(Buffer.from(svg))
    .png()
    .toBuffer();
}
Enter fullscreen mode Exit fullscreen mode

Add route:

app.get("/og/:title", async (req, res) => {
  const image = await generateOgImage(
    req.params.title
  );

  res.set("Content-Type", "image/png");
  res.send(image);
});
Enter fullscreen mode Exit fullscreen mode

Now your OG tag becomes:

<meta
  property="og:image"
  content="https://mysite.com/og/react-seo-guide"
/>
Enter fullscreen mode Exit fullscreen mode

Result

Every shared article automatically gets a branded preview image.

That alone can significantly improve engagement on:

  • Dev.to
  • Twitter/X
  • Reddit
  • LinkedIn
  • Discord

Crawlers also cache aggressively, so dynamic images help prevent stale previews from sticking around too long.

A Cleaner Approach Using @power-seo

After manually wiring this up across multiple projects, I got tired of repeating the same boilerplate.

Especially:

  • Meta tag injection
  • Route matching
  • OG image generation
  • Crawler compatibility
  • SSR edge cases

Instead of manually replacing placeholders, you define SEO data per route.

Example:

import { createSEO } from "@power-seo/core";

const seo = createSEO({
  routes: {
    "/post/:slug": async ({ slug }) => {
      const post = await getPost(slug);

      return {
        title: post.title,
        description: post.description,
        image: post.image
      };
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Then mount middleware:

app.use(seo.middleware());
Enter fullscreen mode Exit fullscreen mode

That’s it.

The important thing is not the library itself. The important thing is the architecture:

  • Generate tags server-side
  • Return crawler-friendly HTML
  • Keep metadata tied to routes
  • Avoid client-only rendering for SEO

The package simply removes repetitive plumbing.

I wrote a deeper breakdown here:

Open Graph React SEO guide

How to Verify Your Open Graph Tags Actually Work

A lot of developers assume tags work because they appear in DevTools.

That’s not enough.

You need to test what crawlers actually see.

Twitter Card Validator

https://cards-dev.twitter.com/validator
Enter fullscreen mode Exit fullscreen mode

LinkedIn Post Inspector

https://www.linkedin.com/post-inspector/
Enter fullscreen mode Exit fullscreen mode

Facebook Sharing Debugger

https://developers.facebook.com/tools/debug/
Enter fullscreen mode Exit fullscreen mode

Paste your URL and verify:

  • Title
  • Description
  • Image
  • Canonical URL

If something looks wrong, the usual causes are:

  • Tags are client-rendered
  • Crawlers cached old HTML
  • Invalid image dimensions
  • Missing absolute URLs

Common Open Graph React SEO Mistakes

1. Using React Helmet Alone

react-helmet is useful for browsers.

It is not enough for crawler compatibility unless paired with SSR or prerendering.

2. Forgetting Absolute URLs

This breaks frequently:

<meta property="og:image" content="/image.png" />
Enter fullscreen mode Exit fullscreen mode

Use:

<meta
  property="og:image"
  content="https://mysite.com/image.png"
/>
Enter fullscreen mode Exit fullscreen mode

3. Wrong Image Size

Recommended dimensions:

1200 × 630
Enter fullscreen mode Exit fullscreen mode

Smaller images often render poorly or fail entirely.

4. Not Caching OG Images

Dynamic generation can become expensive quickly.

Cache aggressively:

res.set(
  "Cache-Control",
  "public, max-age=86400"
);
Enter fullscreen mode Exit fullscreen mode

What I Learned

  • React SPAs often fail social previews because crawlers do not fully execute client-side rendering
  • Open Graph React SEO works best when meta tags are generated server-side
  • Dynamic OG images improve click-through rates more than most developers expect
  • react-helmet helps browsers, but SSR or prerendering fixes crawler visibility
  • You do not need a full Next.js migration to solve this problem
  • Libraries like @[power-seo](https://www.npmjs.com/org/power-seo) are useful because they automate repetitive server-side SEO plumbing.

What’s your current React SEO setup?

Are you using SSR, prerendering, middleware injection, or just hoping crawlers execute your app correctly?

I’m curious what approaches people are using in production right now.

Top comments (0)