Yoginth

April 20, 2025

Serving Open Graph to Bots at hey.xyz: Our Client-Side SPA Approach

At hey.xyz, we built a client-side SPA using Vite and React Router 7. But this created a challenge - how do you serve proper metadata to social media crawlers and search engines when your app renders client-side?

Our solution was to create a dual-architecture system:

  1. Main Web App: CSR app built with Vite + React Router 7 hosted on Cloudflare Pages
  2. OG Service: Minimal SSR Next.js app hosted on Railway that only renders metadata, no UI

The secret sauce is a Cloudflare Worker that acts as our traffic cop. It checks incoming requests for bot user agents, and:

  • If it's a regular user: Pass them to our slick CSR app
  • If it's a bot: Redirect to our specialized OG service

This is the Cloudflare Interface where we connect the Worker with the Web app

image.png

We've set a generous cache policy (30 days) for bot responses since our metadata doesn't change often.

This approach lets us keep our main app fast and interactive for actual users while still having proper social sharing and SEO. No need to sacrifice the benefits of a modern CSR app just to satisfy crawlers.

Here is the flow diagram of how things work

image.png


Honestly, this pattern is so clean and efficient - we're never going back to server-rendering our entire app just for bots.

This strategy helps us deliver millions of requests in a cost-efficient way, keeping our infrastructure costs low while providing the best experience for both users and search engines.

PS: Here is our Cloudflare worker code the checks for all requests

const botRegex = /(Bot|Twitterbot|facebookexternalhit|LinkedInBot|Slackbot|Discordbot|TelegramBot|WhatsApp|Googlebot|Bingbot|Applebot)/i;

export default {
  async fetch(request) {
    const userAgent = request.headers.get("user-agent") || "";

    // If not a bot, pass through
    if (!botRegex.test(userAgent)) {
      return fetch(request);
    }

    // If bot, rewrite to og.hey.xyz
    const url = new URL(request.url);
    const targetUrl = `https://og.hey.xyz${url.pathname}${url.search}`;

    const rewrittenRequest = new Request(targetUrl, {
      method: request.method,
      headers: request.headers,
      body: request.method !== "GET" && request.method !== "HEAD" ? await request.text() : null,
      redirect: "follow"
    });

    const response = await fetch(rewrittenRequest);

    const newHeaders = new Headers(response.headers);
    newHeaders.set("Cache-Control", "public, max-age=2592000, immutable");

    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: newHeaders
    });
  }
};

About Yoginth

I'm Yoginth, the Creator of Hey.xyz ðŸŒ¸