March 29, 2026 · 10 min read

How to Add Dynamic OG Images to Angular with OGPeek API

Angular gives you a powerful component architecture, dependency injection, and a mature router. But generating unique Open Graph images for every route still requires either manual work, server-side rendering tricks, or heavy image processing libraries. Here's how to solve it in under five minutes with a single API URL—no npm packages, no Puppeteer, no Canvas, no build-step changes.

Dynamic OG image for Angular apps generated by OGPeek API

Why Angular Needs Dynamic OG Images

When someone shares a link to your Angular app on Twitter/X, LinkedIn, Slack, or Discord, the platform's crawler fetches the page HTML, reads the og:image meta tag, and requests that image to display as a preview card. If every route on your Angular app points to the same generic image—or has no og:image at all—your links look identical in every feed and get fewer clicks.

This is especially painful for Angular apps because Angular is a single-page application framework by default. Social platform crawlers do not execute JavaScript. They fetch your index.html, read whatever meta tags are in the static HTML, and move on. If your OG tags are only set dynamically via Angular's Meta service at runtime, crawlers will never see them—unless you use server-side rendering.

The traditional approaches each come with significant friction:

There is a simpler approach: point your og:image meta tag at an external API that returns a PNG. Your Angular app stays lean. The image generation happens somewhere else entirely.

How OGPeek Works

OGPeek is an OG image generation API. You construct a URL with your title, subtitle, template, and brand color as query parameters. When that URL is requested, OGPeek renders the image and returns a 1200×630 PNG—the standard size for Open Graph images across all major platforms.

GET https://ogpeek.com/api/v1/og
  ?title=Your+Page+Title
  &subtitle=yourdomain.com
  &template=gradient
  &theme=midnight
  &brandColor=%23FF7A00

That is the entire integration. A GET request returns a PNG. No API keys for the free tier, no SDK to install, no Angular module to import, no build configuration to change. You embed this URL in your HTML's og:image meta tag and every page gets a unique social card based on its title.

Key point: Social platform crawlers follow the URL in your og:image meta tag and fetch whatever is there. They cannot tell the difference between a static PNG hosted on a CDN and an API endpoint that generates a PNG on the fly. Both are just HTTP responses with Content-Type: image/png.

Step 1: Add a Default OG Image to index.html

Every Angular app has a single index.html file in the src/ directory. This is the HTML document that gets served for every route—whether you use client-side rendering or Angular Universal SSR. Adding a default OG image here ensures that every page has at least a baseline social card, even before any JavaScript executes.

Open src/index.html and add the following meta tags inside the <head> block:

<!-- src/index.html -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Your App Name</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- Default Open Graph tags -->
  <meta property="og:title" content="Your App Name">
  <meta property="og:description" content="Your app description here.">
  <meta property="og:type" content="website">
  <meta property="og:url" content="https://yourdomain.com">
  <meta property="og:image"
    content="https://ogpeek.com/api/v1/og?title=Your+App+Name&subtitle=yourdomain.com&template=gradient&theme=midnight&brandColor=%23DD0031">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">

  <!-- Twitter Card tags -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="Your App Name">
  <meta name="twitter:image"
    content="https://ogpeek.com/api/v1/og?title=Your+App+Name&subtitle=yourdomain.com&template=gradient&theme=midnight&brandColor=%23DD0031">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Notice the brandColor=%23DD0031—that is Angular's signature red, URL-encoded. Replace it with your own brand color. This default image covers the case where a crawler fetches your page before Angular hydrates, which is the common scenario for pure client-side Angular apps without SSR.

Why this matters: For a client-side-only Angular app (no SSR), the index.html is the only HTML that social crawlers will see. They do not execute your Angular JavaScript. Setting OG tags here is not optional—it is the only way to get social preview cards without server-side rendering.

Step 2: Dynamic OG Tags with Angular's Meta Service

Angular provides built-in Meta and Title services in @angular/platform-browser for programmatically updating meta tags. This is how you set per-route OG images that change as users navigate your app.

Here is a component that sets its own OG tags on initialization:

// src/app/pages/about/about.component.ts
import { Component, OnInit } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';

@Component({
  selector: 'app-about',
  standalone: true,
  template: `
    <h1>About Us</h1>
    <p>Learn about our team and mission.</p>
  `
})
export class AboutComponent implements OnInit {
  constructor(
    private meta: Meta,
    private title: Title
  ) {}

  ngOnInit(): void {
    const pageTitle = 'About Us';
    const description = 'Learn about our team and mission.';
    const ogImage = 'https://ogpeek.com/api/v1/og?title='
      + encodeURIComponent(pageTitle)
      + '&subtitle=' + encodeURIComponent('yourdomain.com')
      + '&template=gradient&theme=midnight'
      + '&brandColor=%23DD0031';

    this.title.setTitle(pageTitle + ' — Your App');
    this.meta.updateTag({ property: 'og:title', content: pageTitle });
    this.meta.updateTag({ property: 'og:description', content: description });
    this.meta.updateTag({ property: 'og:image', content: ogImage });
    this.meta.updateTag({ property: 'og:image:width', content: '1200' });
    this.meta.updateTag({ property: 'og:image:height', content: '630' });
    this.meta.updateTag({ name: 'twitter:card', content: 'summary_large_image' });
    this.meta.updateTag({ name: 'twitter:title', content: pageTitle });
    this.meta.updateTag({ name: 'twitter:image', content: ogImage });
  }
}

This works at runtime in the browser. However, social crawlers will not see these tags unless you use Angular Universal SSR or prerendering. The Meta service updates the live DOM, but crawlers only read the initial HTML response from the server.

For client-side-only apps, the index.html default from Step 1 is your safety net. For per-route dynamic images, you need Step 3.

Step 3: Angular Universal / SSR for Crawler-Visible OG Tags

Angular Universal (now integrated as @angular/ssr in Angular 17+) renders your Angular components on the server and returns fully-formed HTML to the client. This means social crawlers receive HTML that already contains the correct og:image meta tag for each route—no JavaScript execution needed on their end.

If you already have Angular SSR set up, the Meta service code from Step 2 works without changes. Angular Universal executes your component's ngOnInit on the server, and the Meta.updateTag() calls modify the server-rendered HTML before it is sent to the client.

If you do not have SSR yet, add it with the Angular CLI:

# Add Angular SSR to an existing project
ng add @angular/ssr

This command scaffolds the server entry point, adds Express, and configures your angular.json for server-side rendering. Once deployed, every route your server renders will include the OG tags set by the Meta service.

Prerendering Static Routes

If your Angular app has routes that can be prerendered at build time (like /about, /pricing, /blog/my-post), Angular's build-time prerendering generates static HTML files with all meta tags baked in. This is the most performant option and works on any static hosting provider.

Configure prerendering in angular.json:

// angular.json (partial)
{
  "projects": {
    "your-app": {
      "architect": {
        "prerender": {
          "options": {
            "routes": [
              "/",
              "/about",
              "/pricing",
              "/blog/getting-started",
              "/blog/advanced-features"
            ]
          }
        }
      }
    }
  }
}

Each prerendered HTML file will contain the OGPeek URL in its og:image tag, set by the Meta service during the build-time render. Social crawlers get static HTML with zero server overhead at request time.

Reusable SEO Service Pattern

Calling Meta.updateTag() in every component is repetitive. A better pattern is to create a centralized SeoService that encapsulates all OG tag logic, including the OGPeek URL construction. This keeps your components clean and ensures consistency across your entire Angular app.

// src/app/services/seo.service.ts
import { Injectable } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';

interface SeoConfig {
  title: string;
  description: string;
  subtitle?: string;
  brandColor?: string;
  template?: string;
  theme?: string;
}

@Injectable({ providedIn: 'root' })
export class SeoService {
  private readonly baseUrl = 'https://ogpeek.com/api/v1/og';
  private readonly defaults = {
    subtitle: 'yourdomain.com',
    brandColor: '%23DD0031',
    template: 'gradient',
    theme: 'midnight'
  };

  constructor(
    private meta: Meta,
    private title: Title
  ) {}

  update(config: SeoConfig): void {
    const subtitle = config.subtitle ?? this.defaults.subtitle;
    const brandColor = config.brandColor ?? this.defaults.brandColor;
    const template = config.template ?? this.defaults.template;
    const theme = config.theme ?? this.defaults.theme;

    const ogImage = `${this.baseUrl}?title=${
      encodeURIComponent(config.title)
    }&subtitle=${
      encodeURIComponent(subtitle)
    }&template=${template}&theme=${theme}&brandColor=${brandColor}`;

    this.title.setTitle(`${config.title} — Your App`);
    this.meta.updateTag({ property: 'og:title', content: config.title });
    this.meta.updateTag({ property: 'og:description', content: config.description });
    this.meta.updateTag({ property: 'og:image', content: ogImage });
    this.meta.updateTag({ property: 'og:image:width', content: '1200' });
    this.meta.updateTag({ property: 'og:image:height', content: '630' });
    this.meta.updateTag({ name: 'twitter:card', content: 'summary_large_image' });
    this.meta.updateTag({ name: 'twitter:title', content: config.title });
    this.meta.updateTag({ name: 'twitter:description', content: config.description });
    this.meta.updateTag({ name: 'twitter:image', content: ogImage });
  }
}

Now any component can set its OG tags with a single method call:

// src/app/pages/pricing/pricing.component.ts
import { Component, OnInit } from '@angular/core';
import { SeoService } from '../../services/seo.service';

@Component({
  selector: 'app-pricing',
  standalone: true,
  template: `<h1>Pricing</h1>`
})
export class PricingComponent implements OnInit {
  constructor(private seo: SeoService) {}

  ngOnInit(): void {
    this.seo.update({
      title: 'Pricing',
      description: 'Simple, transparent pricing for every team size.'
    });
  }
}

For dynamic routes like blog posts, pass the data from your route resolver or API response:

// src/app/pages/blog-post/blog-post.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { SeoService } from '../../services/seo.service';

@Component({
  selector: 'app-blog-post',
  standalone: true,
  template: `
    <article>
      <h1>{{ post.title }}</h1>
      <div [innerHTML]="post.content"></div>
    </article>
  `
})
export class BlogPostComponent implements OnInit {
  post: any;

  constructor(
    private route: ActivatedRoute,
    private seo: SeoService
  ) {}

  ngOnInit(): void {
    this.post = this.route.snapshot.data['post'];

    this.seo.update({
      title: this.post.title,
      description: this.post.excerpt,
      subtitle: 'Blog — yourdomain.com'
    });
  }
}

Every blog post gets a unique, branded OG image with its own title—without any manual image creation or changes to the SeoService itself.

Router-Level Integration

For an even more automated approach, you can attach SEO metadata to your route definitions and process them with a global route listener. This eliminates the need for components to call SeoService directly:

// src/app/app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () => import('./pages/home/home.component')
      .then(m => m.HomeComponent),
    data: {
      seo: {
        title: 'Home',
        description: 'Build something great with our platform.'
      }
    }
  },
  {
    path: 'about',
    loadComponent: () => import('./pages/about/about.component')
      .then(m => m.AboutComponent),
    data: {
      seo: {
        title: 'About Us',
        description: 'Learn about our team and mission.'
      }
    }
  },
  {
    path: 'pricing',
    loadComponent: () => import('./pages/pricing/pricing.component')
      .then(m => m.PricingComponent),
    data: {
      seo: {
        title: 'Pricing',
        description: 'Simple, transparent pricing.'
      }
    }
  }
];

Then in your root AppComponent, subscribe to router events and update the SEO tags automatically:

// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { filter, map, mergeMap } from 'rxjs/operators';
import { SeoService } from './services/seo.service';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `<router-outlet />`
})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private seo: SeoService
  ) {}

  ngOnInit(): void {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd),
      map(() => this.activatedRoute),
      map(route => {
        while (route.firstChild) route = route.firstChild;
        return route;
      }),
      mergeMap(route => route.data)
    ).subscribe(data => {
      if (data['seo']) {
        this.seo.update(data['seo']);
      }
    });
  }
}

With this pattern, adding OG images to a new route is as simple as adding a data.seo object to the route definition. No component code changes needed. The SeoService handles the OGPeek URL construction automatically.

OGPeek vs. Angular Universal OG vs. Manual Creation

Here is how the three main approaches compare for Angular projects:

Feature OGPeek API Angular Universal + Sharp Manual (Figma)
Setup time 2 minutes 2–4 hours 5–10 min per image
Dependencies added 0 Sharp, Canvas, or Puppeteer 0
Server load Zero (external API) CPU-intensive per request Zero
Works without SSR Yes (default in index.html) No (requires SSR) Yes
Per-route dynamic images Yes (with SSR or prerender) Yes No (manual per page)
Hosting lock-in None Needs Node.js server None
Scales to 1000+ pages Yes Depends on server capacity No
Custom CSS layout Pre-built templates Full control Full control
Maintenance burden None Ongoing (deps, server, runtime) Per-page manual effort

For most Angular projects—marketing sites, SaaS dashboards, blogs, e-commerce storefronts—OGPeek's pre-built templates cover the common use cases without adding infrastructure complexity to your deployment pipeline.

Customizing Your OG Images

OGPeek supports several parameters to match your brand identity:

All of these parameters can be set dynamically in the SeoService. For example, different sections of your app could use different themes:

// Blog section with midnight theme
this.seo.update({
  title: post.title,
  description: post.excerpt,
  subtitle: 'Engineering Blog',
  theme: 'midnight'
});

// Docs section with dark theme
this.seo.update({
  title: 'API Reference',
  description: 'Complete API documentation.',
  subtitle: 'Documentation',
  theme: 'dark'
});

Testing Your OG Images

After deploying your Angular app, verify your OG images work correctly with these steps:

  1. View the page source (right-click, View Source—not Inspect Element) and confirm the og:image meta tag contains a valid OGPeek URL. If you are using client-side only rendering, check that the default from index.html appears.
  2. Open the OGPeek URL directly in your browser. You should see a 1200×630 PNG with your title rendered on it.
  3. Use a debugger tool like the Twitter Card Validator or Facebook Sharing Debugger to see exactly what crawlers see when they fetch your page.
  4. Share a link in a private Slack channel or Discord server to verify the preview card renders correctly in a real context.

If you are using Angular Universal SSR, the debugger tools should show your per-route title in the OG image. If you are client-side only, they will show the default index.html image.

Performance Considerations

OGPeek caches generated images at the CDN edge. The first request for a given URL renders the image and caches it. Subsequent requests—including from different social platforms sharing the same link—are served from the cache with sub-50ms response times.

Since the og:image URL is only fetched by social platform crawlers (not by your Angular app's actual users in their browsers), there is zero impact on your Angular app's performance metrics. Lighthouse scores, Core Web Vitals, and Angular's built-in performance profiling are completely unaffected. The OGPeek URL is never downloaded by the browser—it exists solely for crawlers.

Frequently Asked Questions

Do I need Angular Universal (SSR) for OGPeek to work?

No. OGPeek works with plain client-side Angular apps. You set a default og:image in your index.html, and social crawlers will use that URL. For per-route dynamic images, Angular Universal or prerendering is recommended so the meta tags are present in the server-rendered HTML. But even without SSR, your default OG image will display on every shared link.

Does OGPeek require any npm packages or Angular dependencies?

No. OGPeek requires zero npm packages. The integration is a URL in your og:image meta tag. Angular's built-in Meta and Title services handle the tag updates. There is nothing to install, no build configuration to change, and no third-party Angular modules to import.

Can I use OGPeek with Angular's new standalone components and signals?

Yes. OGPeek is framework-agnostic—it is just a URL. Whether you use standalone components, NgModules, signals, or traditional Angular patterns, the integration is the same: inject Meta and Title services and set the og:image tag to an OGPeek URL with your page title as a query parameter.

How do I get unique OG images for each route in an Angular SPA?

Create an SeoService that listens to Angular Router events. On each navigation, update the og:image meta tag with a new OGPeek URL containing the current page's title. For social crawlers to see these per-route tags, you need Angular Universal SSR or prerendering—otherwise, crawlers only see the initial index.html.

What image size does OGPeek return for social cards?

OGPeek returns 1200×630 PNG images by default, which is the recommended size for Open Graph images across Facebook, LinkedIn, Twitter/X, Slack, Discord, and other platforms that render link previews.

Start generating OG images for your Angular app

Read the full API documentation, explore templates, and grab your integration URL. Free tier available—no signup required.

Get started free → Read the API docs

Conclusion

Angular's architecture—dependency injection, services, a powerful router, and optional server-side rendering—provides all the building blocks you need for dynamic OG images. But you should not have to build an image rendering pipeline on top of that architecture. OGPeek handles the image generation externally so your Angular app stays focused on what it does best.

For client-side-only apps, add an OGPeek URL to your index.html and every shared link gets a branded social card. For apps with Angular Universal SSR, create an SeoService that constructs OGPeek URLs per route and every page gets its own unique image. Extract the logic into route data objects and the integration is fully declarative.

No npm packages. No headless browsers. No image processing libraries. No server-side rendering required for the basics. Just a GET request that returns a PNG.

Your Angular app stays lean. Your OG images stay dynamic.

More developer APIs from the Peek Suite

← Back to all articles