DEV Community

Yoginth
Yoginth

Posted on

1

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 description

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 description

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
    });
  }
};
Enter fullscreen mode Exit fullscreen mode

Original version can be found here: https://world.hey.com/yoginth/serving-open-graph-to-bots-at-hey-xyz-our-client-side-spa-approach-8b4af2a3

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (0)

Billboard image

Try REST API Generation for Snowflake

DevOps for Private APIs. Automate the building, securing, and documenting of internal/private REST APIs with built-in enterprise security on bare-metal, VMs, or containers.

  • Auto-generated live APIs mapped from Snowflake database schema
  • Interactive Swagger API documentation
  • Scripting engine to customize your API
  • Built-in role-based access control

Learn more

👋 Kindness is contagious

Value this insightful article and join the thriving DEV Community. Developers of every skill level are encouraged to contribute and expand our collective knowledge.

A simple “thank you” can uplift someone’s spirits. Leave your appreciation in the comments!

On DEV, exchanging expertise lightens our path and reinforces our bonds. Enjoyed the read? A quick note of thanks to the author means a lot.

Okay