Bun Open Graph Meta Tags: Add OG to Your Bun.js App
How to add og:title, og:image, og:description, and Twitter card meta tags to a Bun application — using Bun.serve, ElysiaJS, or a Bun + React/Next.js setup.
Bun.serve: vanilla HTML with OG tags
Bun's built-in HTTP server (Bun.serve) lets you return HTML responses directly. Inject OG meta tags server-side so scrapers receive them in the initial response:
// server.ts
function renderPage(title: string, description: string, image: string, url: string): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>${title}</title>
<meta name="description" content="${description}" />
<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 property="og:type" content="article" />
<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}" />
<link rel="canonical" href="${url}" />
</head>
<body>
<h1>${title}</h1>
</body>
</html>`;
}
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
if (url.pathname.startsWith('/blog/')) {
const slug = url.pathname.replace('/blog/', '');
// Replace with your data source
const post = await getPost(slug);
if (!post) return new Response('Not found', { status: 404 });
const html = renderPage(
post.title,
post.excerpt,
`https://yourdomain.com/og/${slug}.png`,
req.url,
);
return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' },
});
}
return new Response('Hello from Bun!');
},
});ElysiaJS: OG tags in HTML responses
ElysiaJS is the most popular web framework built for Bun. Use it with @elysiajs/html or return raw HTML strings:
// index.ts
import { Elysia } from 'elysia';
import { html } from '@elysiajs/html';
const app = new Elysia()
.use(html())
.get('/blog/:slug', async ({ params: { slug } }) => {
const post = await getPost(slug); // your data fetch
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>${post.title}</title>
<meta property="og:title" content="${post.title}" />
<meta property="og:description" content="${post.excerpt}" />
<meta property="og:image" content="https://yourdomain.com/og/${slug}.png" />
<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:image" content="https://yourdomain.com/og/${slug}.png" />
<link rel="canonical" href="https://yourdomain.com/blog/${slug}" />
</head>
<body>
<h1>${post.title}</h1>
</body>
</html>`;
})
.listen(3000);
console.log('Listening on', app.server?.url);Bun + Next.js
If you're using Bun as the runtime for a Next.js project (bun dev), add OG tags in the generateMetadata export of your page — Next.js handles server-side injection automatically:
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [{ url: `https://yourdomain.com/og/${params.slug}.png`, width: 1200, height: 630 }],
type: 'article',
},
twitter: {
card: 'summary_large_image',
images: [`https://yourdomain.com/og/${params.slug}.png`],
},
};
}Bun-specific gotchas
- Missing Content-Type header — always set
Content-Type: text/html; charset=utf-8when returning HTML fromBun.serve. Without it, some scrapers may not parse the response correctly. - Client-side rendering — if you return a bare JavaScript bundle and render HTML in the browser, OG tags won't be visible to scrapers. Use SSR or a static build.
- OG image URL must be absolute — always use
https://yourdomain.com/..., not relative paths.
Verify your Bun app's OG tags
After deploying, paste your URL into OGFixer to preview how your Bun app's links appear on Twitter, LinkedIn, Slack, and Discord — and catch any OG tag issues before they go live.