Node.js Open Graph Meta Tags: Express, Fastify & Vanilla Node
Add og:title, og:image, og:description and Twitter card tags to any Node.js app — Express, Fastify, or vanilla http — with copy-paste examples.
How OG tags work in server-rendered Node apps
Open Graph tags live inside the <head> of your HTML response. In a Node.js app you control every byte of that HTML — so you set OG tags by writing the correct <meta> elements into the template or string you send back to the client. There is no magic framework — just string interpolation.
Express.js: inline head helper
// server.js (Express)
const express = require('express');
const app = express();
function ogHead({ title, description, image, url }) {
return `
<meta property="og:type" content="website" />
<meta property="og:title" content="${title}" />
<meta property="og:description" content="${description}" />
<meta property="og:image" content="${image}" />
<meta property="og:url" content="${url}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="${title}" />
<meta name="twitter:description"content="${description}" />
<meta name="twitter:image" content="${image}" />
`;
}
app.get('/product/:slug', async (req, res) => {
const product = await getProduct(req.params.slug);
res.send(`<!DOCTYPE html>
<html><head>
<title>${product.name}</title>
${ogHead({
title: product.name,
description: product.tagline,
image: 'https://yourdomain.com/og/${product.slug}.png',
url: 'https://yourdomain.com/product/${product.slug}',
})}
</head><body>...</body></html>`);
});Express + EJS or Pug template
If you use a template engine, pass OG values as locals:
// EJS layout head partial — views/partials/head.ejs
<title><%= title %></title>
<meta property="og:title" content="<%= ogTitle || title %>" />
<meta property="og:description" content="<%= ogDescription %>" />
<meta property="og:image" content="<%= ogImage %>" />
<meta property="og:url" content="<%= ogUrl %>" />
<meta name="twitter:card" content="summary_large_image" />
// route handler
res.render('product', {
title: product.name,
ogTitle: product.name,
ogDescription: product.tagline,
ogImage: `https://yourdomain.com/og/${product.slug}.png`,
ogUrl: req.protocol + '://' + req.get('host') + req.originalUrl,
});Fastify with @fastify/view
import Fastify from 'fastify';
import view from '@fastify/view';
import ejs from 'ejs';
const app = Fastify();
app.register(view, { engine: { ejs } });
app.get('/post/:id', async (req, reply) => {
const post = await getPost(req.params.id);
return reply.view('post.ejs', {
ogTitle: post.title,
ogDescription: post.excerpt,
ogImage: post.coverImage,
ogUrl: `https://yourdomain.com/post/${post.id}`,
});
});Generate OG images server-side with Satori or Sharp
For dynamic OG images, use Satori (renders JSX to SVG then PNG) or Sharp (image composition). Expose a /og/:slug.png route and point your og:image at it.
// /og/:slug route using @vercel/og (satori under the hood)
import satori from 'satori';
import sharp from 'sharp';
import { readFileSync } from 'fs';
app.get('/og/:slug.png', async (req, res) => {
const post = await getPost(req.params.slug);
const font = readFileSync('./fonts/Inter-Bold.ttf');
const svg = await satori(
{ type: 'div', props: { style: { fontSize: 48, color: '#fff', background: '#0a0a0a', width: 1200, height: 630, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 48 }, children: post.title } },
{ width: 1200, height: 630, fonts: [{ name: 'Inter', data: font, weight: 700 }] }
);
const png = await sharp(Buffer.from(svg)).png().toBuffer();
res.set('Content-Type', 'image/png').send(png);
});Top mistakes in Node.js OG implementations
- Relative image URLs: og:image must be an absolute URL including protocol and domain.
- Missing og:type: Always include
og:type— usewebsiteorarticle. - CDN caching stale previews: Set
Cache-Control: public, max-age=3600on og:image routes but short TTLs if content changes. - HTML entity escaping: Escape
&,<,>,"in meta content values or scrapers will misparse them.
Verify your Node.js OG tags before going live
After implementing tags, paste your URL into OGFixer to see exactly how Twitter, Slack, Discord, and LinkedIn will render your preview — including image dimensions, missing required tags, and caching warnings.