Social media preview cards with OG-Image meta tags floating in dark space

    OG-Image Best Practices for SPAs: Making Your Vibe Coding Projects Shareable

    Till FreitagTill Freitag14. April 20264 min Lesezeit
    Till Freitag

    TL;DR: „SPAs don't deliver OG meta tags – social previews are empty. The fix: pre-rendering for static tags + dynamic OG image generation via Edge Functions for individual pages."

    — Till Freitag

    You built an app with Lovable or Bolt. It works. It looks great. But when you share the link on LinkedIn:

    • No image – just a generic placeholder icon
    • No title – or worse: "React App"
    • No description – LinkedIn shows nothing

    The problem isn't your content. The problem is the architecture.

    Why SPAs Have No Social Previews

    Single Page Applications render everything in the browser. When LinkedIn, Twitter, or WhatsApp crawl your link, they don't execute JavaScript. They only get the empty index.html:

    <!DOCTYPE html>
    <html>
    <head>
      <title>React App</title>
    </head>
    <body>
      <div id="root"></div>
      <script src="/assets/index.js"></script>
    </body>
    </html>

    No og:title. No og:image. No og:description. No preview.

    What Open Graph Tags Are – And Why They Matter

    Open Graph (OG) tags are meta tags in your HTML <head> that social media platforms read:

    <meta property="og:title" content="Your Page Title" />
    <meta property="og:description" content="Description for the preview" />
    <meta property="og:image" content="https://example.com/og-image.jpg" />
    <meta property="og:url" content="https://example.com/page" />
    <meta property="og:type" content="website" />

    The SPA problem: These tags are set at runtime via JavaScript (e.g., with react-helmet-async). But crawlers don't wait for JavaScript.

    The Solution: 3-Layer Strategy

    Layer 1: Pre-Rendering for Static OG Tags

    Playwright SSG pre-renders each page and bakes the meta tags into HTML:

    // playwright-ssg.ts
    const pages = ['/blog/my-article', '/services/consulting'];
    
    for (const path of pages) {
      const page = await browser.newPage();
      await page.goto(`http://localhost:5173${path}`);
      await page.waitForSelector('meta[property="og:title"]');
      const html = await page.content();
      // Save as static HTML file
    }

    Result: Crawlers get fully rendered pages with all OG tags.

    Playwright SSG Tutorial: Step by Step

    Layer 2: Dynamic OG Images with Edge Functions

    For blog posts or product pages, you want individual preview images. Vercel Edge Functions generate these on-the-fly:

    // api/og-image.tsx (Vercel Edge Function)
    import { ImageResponse } from '@vercel/og';
    
    export const config = { runtime: 'edge' };
    
    export default function handler(req: Request) {
      const { searchParams } = new URL(req.url);
      const title = searchParams.get('title') || 'Default Title';
    
      return new ImageResponse(
        <div style={{
          display: 'flex',
          fontSize: 48,
          background: 'linear-gradient(135deg, #1a2744, #0d1b2a)',
          color: 'white',
          width: '100%',
          height: '100%',
          padding: 60,
          alignItems: 'center',
        }}>
          {title}
        </div>,
        { width: 1200, height: 630 }
      );
    }

    In your HTML:

    <meta property="og:image" content="https://example.com/api/og-image?title=My+Article" />

    Layer 3: Fallback Image as Safety Net

    Always define a default OG image in your index.html:

    <meta property="og:image" content="https://example.com/og-default.jpg" />

    If pre-rendering or Edge Function fails, LinkedIn at least shows your brand image instead of an empty box.

    The OG-Image Checklist

    Format & Size

    • 1200×630px – the universal OG image format
    • < 1 MB file size (LinkedIn crops larger images)
    • WebP or JPG – PNG only when transparency is needed
    • No important content in the outer 10% – platforms crop differently

    Meta Tags

    • og:image with absolute URL (not relative!)
    • Set og:image:width and og:image:height
    • og:image:alt for accessibility
    • Set twitter:card to summary_large_image

    Technical

    • HTTPS mandatory – HTTP images get blocked
    • Set cache headers (Cache-Control: public, max-age=31536000)
    • No redirects – OG image URL must point directly to the image
    • Use a CDN – fast load times for crawlers

    Common Mistakes in Vibe Coding Projects

    1. react-helmet-async Alone Isn't Enough

    react-helmet-async sets tags client-side. Crawlers still don't see them. You always need pre-rendering.

    2. Relative Image URLs

    <!-- ❌ Wrong -->
    <meta property="og:image" content="/images/preview.jpg" />
    
    <!-- ✅ Correct -->
    <meta property="og:image" content="https://example.com/images/preview.jpg" />

    3. No Twitter Card Fallback

    Twitter/X reads og:image but prefers twitter:image. Set both:

    <meta property="og:image" content="https://example.com/og.jpg" />
    <meta name="twitter:image" content="https://example.com/og.jpg" />
    <meta name="twitter:card" content="summary_large_image" />

    4. OG Image Gets Cached

    LinkedIn and Facebook aggressively cache OG images. After changes, manually invalidate the cache:

    Our Implementation: till-freitag.com

    On our own website, we use exactly this strategy:

    1. react-helmet-async sets OG tags per page in React code
    2. Playwright SSG pre-renders all pages – OG tags are in static HTML
    3. Static OG images in WebP for blog posts from the asset registry
    4. Absolute URLs via a central BASE_URL configuration

    Result: Every shared link shows a perfect preview image on LinkedIn, Twitter, and WhatsApp.

    Quick Win: OG Image in 5 Minutes

    If you want to start immediately, here's the minimal approach:

    1. Create a 1200×630px image with your brand
    2. Place it in /public/og-image.jpg
    3. Add to index.html:
    <meta property="og:image" content="https://your-domain.com/og-image.jpg" />
    <meta property="og:image:width" content="1200" />
    <meta property="og:image:height" content="630" />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:image" content="https://your-domain.com/og-image.jpg" />

    This doesn't replace dynamic OG images – but it's much better than nothing.


    Next Steps

    OG images are just one piece of the SEO puzzle for vibe coding projects. The complete stack includes pre-rendering, schema markup, and edge delivery.

    Vibe Coding SEO Guide: The Complete OverviewAutomate JSON-LD Schema for SPAsLovable → GitHub → Vercel: The Production Workflow

    👉 Free SEO Audit for Your Vibe Coding Project →

    TeilenLinkedInWhatsAppE-Mail

    Verwandte Artikel

    Rocket made of code elements launching through search result pages with Lighthouse Score 100
    14. April 20266 min

    Vibe Coding & SEO: Why AI-Generated Apps Stay Invisible – And How We Fix It

    Lovable, Bolt, v0 – Vibe Coding tools produce SPAs that Google can't see. Our playbook makes them SEO-ready: SSG, Schema…

    Weiterlesen
    Google Search Console dashboard with performance graphs and coverage reports
    14. April 20265 min

    Google Search Console for Vibe Coding Projects: Setup, Debugging & Indexing

    Your Lovable app is live on Vercel – but Google isn't indexing anything? How to set up Search Console, debug crawling is…

    Weiterlesen
    Pipeline diagram with three stations: Lovable, GitHub, Vercel
    14. April 20264 min

    Lovable → GitHub → Vercel: The Complete Deployment Flow for SEO-Ready Apps

    Lovable generates the app, GitHub versions the code, Vercel delivers with < 50ms TTFB. This guide shows the complete flo…

    Weiterlesen
    Headless browser rendering SPA pages as static HTML for search engines
    14. April 20266 min

    Playwright SSG Tutorial: How to Make Lovable Apps Visible to Google

    SPAs are invisible to search engines. With Playwright, you can render any Lovable app into static HTML – automated, on e…

    Weiterlesen
    AI Website Builder Comparison – Framer, Webflow AI, Wix AI, Durable, and Lovable Stack SEO test
    10. April 20266 min

    AI Website Builder Compared: Framer vs. Webflow AI vs. Wix AI vs. Durable vs. Lovable Stack

    Five ways to build a website compared on SEO: Framer, Webflow AI, Wix AI, Durable – and the Lovable + GitHub + Vercel st…

    Weiterlesen
    Lovable vs. Bolt vs. v0 – Which AI Web Builder Is Right for You?
    24. Februar 20264 min

    Lovable vs. Bolt vs. v0 – Which AI Web Builder Is Right for You?

    Lovable, Bolt.new, or v0? We compare the three most popular AI web builders – with honest assessments, pricing, and clea…

    Weiterlesen
    Connected schema nodes as a glowing graph on dark background
    14. April 20265 min

    Automate JSON-LD Schema for SPAs: Structured Data Without a Backend

    SPAs have no structured data – and Google shows no rich snippets. This tutorial shows how to automatically generate JSON…

    Weiterlesen
    Futuristic code editor windows with turquoise and blue accents on dark background
    10. März 20267 min

    We're Not a Web Agency – And That's the Point

    Looking for a web agency? You're in the wrong place. Looking for someone to solve your digital problem? You've found us.…

    Weiterlesen
    No-Code vs. Custom Development – When Is Each the Right Choice? (2026)
    25. Juni 20252 min

    No-Code vs. Custom Development – When Is Each the Right Choice? (2026)

    No-code, vibe coding, or custom development? We break down when each approach makes sense – with real examples and a dec…

    Weiterlesen