Meteor.js Open Graph Meta Tags: Add OG to Your Meteor App

How to add og:title, og:image, og:description, and Twitter card meta tags to Meteor.js applications — using server-side rendering with ostrio:flow-router-extra, kadira:flow-router, and the ostrio:spiderable package.

Why Meteor OG tags need special handling

Meteor apps are single-page applications (SPAs) by default. Social media crawlers — like Twitterbot, LinkedInbot, and Slackbot — don't execute JavaScript, so they only see the initial HTML response. In a standard Meteor SPA, that HTML has no OG tags until React or Blaze renders the page.

To serve correct OG tags to social crawlers, you need either:

  • A spiderable/prerendering service that renders JS for bots
  • Server-side rendering (SSR) via a Next.js frontend backed by Meteor DDP
  • Static OG fallbacks in your index.html for the homepage

Option 1: ostrio:spiderable (prerendering)

The ostrio:spiderable package intercepts requests from known crawler user-agents and serves a pre-rendered HTML snapshot. Install it and combine with dynamic head tag management:

# Install spiderable
meteor add ostrio:spiderable-middleware

# Install head management
meteor npm install react-helmet-async
// server/main.js
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import SpiderablePkg from 'meteor/ostrio:spiderable-middleware';

const { Spiderable } = SpiderablePkg;

Meteor.startup(() => {
  WebApp.connectHandlers.use(new Spiderable({
    rootURL: 'https://yourdomain.com',
    serviceURL: 'https://prerender.io/https://yourdomain.com',
    auth: 'prerender_token',
    only: [
      /^/blog/.*/,
      /^/product/.*/,
    ],
  }));
});
// client/PostPage.jsx
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { useFind, useSubscribe } from 'meteor/react-meteor-data';

export function PostPage({ slug }) {
  useSubscribe('posts.single', slug);
  const post = Posts.findOne({ slug });

  if (!post) return null;

  return (
    <>
      <Helmet>
        <title>{post.title}</title>
        <meta name="description" content={post.excerpt} />
        <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={`https://yourdomain.com/blog/${slug}`} />
        <meta property="og:type" content="article" />
        <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} />
      </Helmet>
      <article>...</article>
    </>
  );
}

Option 2: Static OG tags in index.html

For the homepage and a limited number of high-priority pages, add OG tags directly to your client/main.html or public/index.html. These are injected before JavaScript runs, so crawlers see them immediately:

<!-- client/main.html -->
<head>
  <title>Your Meteor App</title>
  <meta name="description" content="Your app description" />
  <meta property="og:title" content="Your Meteor App" />
  <meta property="og:description" content="Your app description" />
  <meta property="og:image" content="https://yourdomain.com/og-default.png" />
  <meta property="og:url" content="https://yourdomain.com" />
  <meta property="og:type" content="website" />
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="Your Meteor App" />
  <meta name="twitter:description" content="Your app description" />
  <meta name="twitter:image" content="https://yourdomain.com/og-default.png" />
  <link rel="canonical" href="https://yourdomain.com" />
</head>

Note: Static tags in main.html apply to all routes. Use them as homepage/default fallbacks. Per-route tags require prerendering or SSR.

Option 3: Meteor server-side rendering

Use Meteor's built-in SSR support with meteor/server-renderto inject per-route OG tags in the server response:

# Add server-render package
meteor add server-render
// server/ssr.js
import { onPageLoad } from 'meteor/server-render';
import { Posts } from '/imports/api/posts';

onPageLoad(async (sink) => {
  const url = sink.request.url;
  const slugMatch = url.pathname.match(/^/blog/([^/]+)/);

  if (slugMatch) {
    const slug = slugMatch[1];
    const post = await Posts.findOneAsync({ slug });

    if (post) {
      // Inject OG tags into the <head>
      sink.appendToHead(`
        <title>${escapeHtml(post.title)}</title>
        <meta name="description" content="${escapeHtml(post.excerpt)}" />
        <meta property="og:title" content="${escapeHtml(post.title)}" />
        <meta property="og:description" content="${escapeHtml(post.excerpt)}" />
        <meta property="og:image" content="${escapeHtml(post.coverImage)}" />
        <meta property="og:url" content="https://yourdomain.com/blog/${escapeHtml(slug)}" />
        <meta property="og:type" content="article" />
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:title" content="${escapeHtml(post.title)}" />
        <meta name="twitter:image" content="${escapeHtml(post.coverImage)}" />
        <link rel="canonical" href="https://yourdomain.com/blog/${escapeHtml(slug)}" />
      `);
    }
  }
});

function escapeHtml(str = '') {
  return String(str).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
}

This approach injects OG tags directly into the server-rendered HTML before the page is sent, so crawlers see them without executing JavaScript.

Common Meteor OG mistakes

  • Relying on client-side react-helmet only — Twitterbot and LinkedInbot don't run JavaScript, so client-rendered tags are invisible to them.
  • No prerendering service — without spiderable or SSR, crawlers see an empty app shell with no metadata.
  • Dynamic content after Meteor data subscription — even with SSR, if your post data loads via DDP subscription after the initial response, the tags won't be in the crawler's snapshot.

Verify your Meteor OG tags

After setting up prerendering or SSR, paste your Meteor app URL into OGFixer to see how your link previews appear on Twitter, LinkedIn, Slack, and Discord before sharing live.