NestJS Open Graph Meta Tags: Add OG to Your NestJS App

How to add og:title, og:image, og:description, and Twitter card meta tags to a NestJS application — using server-side rendering, Handlebars/EJS views, or a headless API + Next.js frontend.

NestJS with server-side rendering (Handlebars)

If your NestJS app renders HTML views using @nestjs/platform-express with Handlebars or EJS, inject OG tags in the view template and pass values from your controller:

// app.module.ts — enable Handlebars view engine
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  app.setBaseViewsDir(join(__dirname, '..', 'views'));
  app.setViewEngine('hbs');
  await app.listen(3000);
}
bootstrap();
// posts.controller.ts
import { Controller, Get, Param, Render } from '@nestjs/common';
import { PostsService } from './posts.service';

@Controller('blog')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Get(':slug')
  @Render('post')
  async getPost(@Param('slug') slug: string) {
    const post = await this.postsService.findBySlug(slug);
    return {
      title: post.title,
      description: post.excerpt,
      ogImage: `https://yourdomain.com/og/${slug}.png`,
      canonicalUrl: `https://yourdomain.com/blog/${slug}`,
    };
  }
}
{{!-- views/post.hbs --}}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <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="{{ogImage}}" />
  <meta property="og:url"         content="{{canonicalUrl}}" />
  <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="{{ogImage}}" />
  <link rel="canonical" href="{{canonicalUrl}}" />
</head>
<body>
  {{> content}}
</body>
</html>

NestJS as headless API (Next.js / React frontend)

If NestJS is your backend API and you use Next.js or another SSR frontend, add OG meta tags in the frontend layer — not in NestJS itself. The frontend fetches data from NestJS and injects meta tags server-side:

// app/blog/[slug]/page.tsx (Next.js App Router)
export async function generateMetadata({ params }: { params: { slug: string } }) {
  const post = await fetch(`https://api.yourdomain.com/blog/${params.slug}`).then(r => r.json());

  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',
      title: post.title,
      description: post.excerpt,
      images: [`https://yourdomain.com/og/${params.slug}.png`],
    },
    alternates: { canonical: `https://yourdomain.com/blog/${params.slug}` },
  };
}

Dynamic OG image generation in NestJS

You can generate OG images on-demand by creating a dedicated endpoint in NestJS that returns a PNG image using a library like @vercel/og, sharp, or canvas:

// og.controller.ts
import { Controller, Get, Query, Res } from '@nestjs/common';
import { Response } from 'express';
import sharp from 'sharp';

@Controller('og')
export class OgController {
  @Get('image')
  async generateOgImage(
    @Query('title') title: string,
    @Query('description') description: string,
    @Res() res: Response,
  ) {
    const svgBuffer = Buffer.from(`
      <svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
        <rect width="1200" height="630" fill="#0f0f0f"/>
        <text x="60" y="200" font-size="60" font-family="sans-serif" fill="white">
          ${title}
        </text>
        <text x="60" y="300" font-size="28" font-family="sans-serif" fill="#aaa">
          ${description}
        </text>
      </svg>
    `);

    const png = await sharp(svgBuffer).png().toBuffer();

    res.setHeader('Content-Type', 'image/png');
    res.setHeader('Cache-Control', 'public, max-age=86400, s-maxage=86400');
    res.send(png);
  }
}

Then reference the endpoint as your og:image URL:

<meta property="og:image" content="https://yourdomain.com/og/image?title=My+Post&description=A+great+post" />

Common NestJS OG mistakes

  • Returning JSON instead of HTML — social scrapers can't read OG tags from JSON API responses. If your NestJS app only serves a JSON API, OG must live in your frontend layer.
  • Missing Content-Type header — ensure your server returns text/html for rendered pages so scrapers parse them correctly.
  • Dynamic routes with async data but no SSR — if the page is rendered client-side only, OG tags won't be visible to scrapers. NestJS must inject them in the initial HTML response.

Verify your NestJS OG tags

After deploying, paste your URL into OGFixer to see exactly how your NestJS app's link previews appear on Twitter, LinkedIn, Slack, and Discord — and catch missing or broken tags before they go live.