StencilJS Open Graph Meta Tags: OG for Web Components

How to add og:title, og:image, og:description, and Twitter card meta tags in a StencilJS app — using the Head component, stencil-router, or prerendering.

Why Stencil OG is tricky

Stencil compiles web components to standard Custom Elements that run natively in any browser. By default, Stencil apps are client-side rendered (CSR) — the HTML shell is mostly empty, and components render after JavaScript loads.

Social crawlers (Twitterbot, LinkedInBot, Discordbot) don't execute JavaScript. They parse the raw HTML and stop there. If your Stencil app delivers an empty <head> with no OG tags, your social previews will be blank or broken.

The two reliable approaches are: (1) use Stencil's static site generation (SSG) / prerendering to embed tags at build time, or (2) use a server that injects per-route meta tags into the HTML shell before delivery.

Using document.head manipulation in Stencil

For client-side-only use cases (where crawlers aren't a concern), you can manipulate the document head from within a Stencil component's lifecycle hooks. This won't help social crawlers, but it keeps the document title/description in sync for users.

// src/components/my-page/my-page.tsx
import { Component, Prop, h } from '@stencil/core';

@Component({
  tag: 'my-page',
  shadow: false,
})
export class MyPage {
  @Prop() pageTitle: string;
  @Prop() pageDescription: string;
  @Prop() ogImage: string;
  @Prop() pageUrl: string;

  componentWillLoad() {
    this.setOgTags();
  }

  setOgTags() {
    // Update title
    document.title = this.pageTitle;

    // Helper to set or update a meta tag
    const setMeta = (attr: string, value: string, content: string) => {
      let el = document.querySelector(`meta[${attr}="${value}"]`);
      if (!el) {
        el = document.createElement('meta');
        el.setAttribute(attr, value);
        document.head.appendChild(el);
      }
      el.setAttribute('content', content);
    };

    setMeta('property', 'og:title',       this.pageTitle);
    setMeta('property', 'og:description', this.pageDescription);
    setMeta('property', 'og:image',       this.ogImage);
    setMeta('property', 'og:url',         this.pageUrl);
    setMeta('property', 'og:type',        'website');
    setMeta('name',     'twitter:card',   'summary_large_image');
    setMeta('name',     'twitter:title',  this.pageTitle);
    setMeta('name',     'twitter:image',  this.ogImage);
  }

  render() {
    return <main>/* page content */</main>;
  }
}

Note: This only works for logged-in users or browser traffic — social crawlers will not see these tags because they don't run JavaScript.

Stencil static site generation (prerendering)

Stencil has built-in prerendering support. When you prerender your routes, Stencil executes your components during the build and writes the rendered HTML (including <head> tags) to static files. Social crawlers receive the full HTML — OG tags included.

// stencil.config.ts
import { Config } from '@stencil/core';

export const config: Config = {
  outputTargets: [
    {
      type: 'www',
      serviceWorker: null,
      prerenderConfig: './prerender.config.ts',
    },
  ],
};

// prerender.config.ts
import { PrerenderConfig } from '@stencil/core';

export const config: PrerenderConfig = {
  entryUrls: ['/', '/about', '/blog'],
  crawlUrls: true,
};

In your component, use @stencil/core/internal's Helmet or manipulate the head via document.head in componentWillLoad — Stencil's prerenderer executes this code and captures the result in the static HTML output.

Per-route meta with @stencil/router

If you're using @stencil/router, you can set OG tags per route by updating the document head in each routed component's componentWillLoad. Combine this with prerendering for crawler-friendly output.

// src/components/app-root/app-root.tsx
import { Component, h } from '@stencil/core';
import { Router, Route } from '@stencil/router';

@Component({
  tag: 'app-root',
  shadow: false,
})
export class AppRoot {
  render() {
    return (
      <Router>
        <Route path="/" component="page-home" />
        <Route path="/blog/:slug" component="page-blog-post" />
        <Route path="/about" component="page-about" />
      </Router>
    );
  }
}

// src/components/page-blog-post/page-blog-post.tsx
@Component({ tag: 'page-blog-post', shadow: false })
export class PageBlogPost {
  @Prop() match: MatchResults;

  async componentWillLoad() {
    const post = await fetchPost(this.match.params.slug);
    document.title = post.title;
    // Set OG tags via setMeta helper (see above)
    setMeta('property', 'og:title',       post.title);
    setMeta('property', 'og:description', post.excerpt);
    setMeta('property', 'og:image',       post.ogImage);
    setMeta('property', 'og:url', `https://yourdomain.com/blog/${post.slug}`);
  }
}

OG tag checklist for StencilJS

  • Enable Stencil's prerendering for crawler-visible OG tags.
  • Set OG tags in componentWillLoad so they are captured during prerendering.
  • Use absolute URLs for all og:image values.
  • Set twitter:card as summary_large_image.
  • Image size: 1200×630 px PNG or JPG, under 5 MB.
  • Test prerendered output with OGFixer after each deploy.

Verify your Stencil OG tags instantly

Paste any Stencil app URL into OGFixer to preview how links look on Twitter, LinkedIn, Discord, and Slack.

Related Guides