Qwik Open Graph Meta Tags: Add OG to Your Qwik City App

How to add og:title, og:image, og:description, and Twitter card meta tags to Qwik City apps using useDocumentHead, routeLoader, and server-side rendering.

Why Qwik OG needs server-side rendering

Qwik is a resumable framework designed for instant-loading web apps. Unlike traditional React or Vue SPAs, Qwik City (the meta-framework) performs server-side rendering by default — which means your <head> tags are already present in the HTML response. Social crawlers like Twitterbot, LinkedInBot, and Discordbot never execute JavaScript; they only parse the raw HTML. Qwik's SSR-first architecture gives you a natural advantage here.

The key API is useDocumentHead combined with routeLoader$ for dynamic per-page data. This guide covers both static and dynamic OG tag patterns for Qwik City routes.

useDocumentHead for per-page OG tags

Every Qwik City route can export a head function that returns document head configuration. This is the primary way to set static OG tags per route.

// src/routes/index.tsx
import { component$ } from '@builder.io/qwik';
import type { DocumentHead } from '@builder.io/qwik-city';

export default component$(() => {
  return <div>Home page content</div>;
});

export const head: DocumentHead = {
  title: 'My Qwik App — Build Instantly',
  meta: [
    {
      name: 'description',
      content: 'A blazing-fast Qwik City app with resumable hydration.',
    },
    // Open Graph
    { property: 'og:type',        content: 'website' },
    { property: 'og:title',       content: 'My Qwik App — Build Instantly' },
    { property: 'og:description', content: 'A blazing-fast Qwik City app.' },
    { property: 'og:image',       content: 'https://yourdomain.com/og/home.png' },
    { property: 'og:url',         content: 'https://yourdomain.com/' },
    // Twitter card
    { name: 'twitter:card',        content: 'summary_large_image' },
    { name: 'twitter:title',       content: 'My Qwik App — Build Instantly' },
    { name: 'twitter:description', content: 'A blazing-fast Qwik City app.' },
    { name: 'twitter:image',       content: 'https://yourdomain.com/og/home.png' },
  ],
};

The head export is processed on the server and injected into the HTML response before sending to the client — guaranteeing that crawlers see all your meta tags immediately.

routeLoader$ for dynamic OG tags

For blog posts, product pages, or any route where OG data comes from an API or database, use routeLoader$ to fetch data server-side, then reference it in a dynamic head export function.

// src/routes/blog/[slug]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import type { DocumentHead } from '@builder.io/qwik-city';

export const usePost = routeLoader$(async ({ params }) => {
  // Fetch from your CMS, database, or API
  const post = await fetchPost(params.slug);
  return post;
});

export default component$(() => {
  const post = usePost();
  return (
    <article>
      <h1>{post.value.title}</h1>
      <p>{post.value.content}</p>
    </article>
  );
});

// Dynamic head using loader data
export const head: DocumentHead = ({ resolveValue, params }) => {
  const post = resolveValue(usePost);
  return {
    title: `${post.title} | My Blog`,
    meta: [
      { name: 'description',         content: post.excerpt },
      { property: 'og:type',         content: 'article' },
      { property: 'og:title',        content: post.title },
      { property: 'og:description',  content: post.excerpt },
      {
        property: 'og:image',
        content: post.ogImage ?? `https://yourdomain.com/og/${params.slug}.png`,
      },
      { property: 'og:url',          content: `https://yourdomain.com/blog/${params.slug}` },
      { name: 'twitter:card',        content: 'summary_large_image' },
      { name: 'twitter:title',       content: post.title },
      { name: 'twitter:description', content: post.excerpt },
      {
        name: 'twitter:image',
        content: post.ogImage ?? `https://yourdomain.com/og/${params.slug}.png`,
      },
    ],
  };
};

Because routeLoader$ runs on the server during SSR, the meta tags are embedded in the HTML before it reaches the browser — no JavaScript execution required by the crawler.

Twitter card setup

Twitter (X) requires twitter:card to be set explicitly — it won't fall back to OG tags alone. Use summary_large_image for a full-width preview card with a large image. Add these to your head meta array:

// Add to your head meta array
{ name: 'twitter:card',        content: 'summary_large_image' },
{ name: 'twitter:site',        content: '@yourhandle' },
{ name: 'twitter:creator',     content: '@authorhandle' },
{ name: 'twitter:title',       content: 'Your page title' },
{ name: 'twitter:description', content: 'Your page description' },
{ name: 'twitter:image',       content: 'https://yourdomain.com/og/image.png' },
{ name: 'twitter:image:alt',   content: 'Descriptive alt text for the image' },

Your og:image must be at least 1200×630 px and under 5 MB. Twitter caches cards aggressively — use the Twitter Card Validator to force a cache refresh during development.

OG tag checklist for Qwik City

  • Export a head constant or function from every route file.
  • Use routeLoader$ for dynamic routes — never fetch data client-side for OG tags.
  • Set og:title, og:description, og:image, og:url, and og:type.
  • Add twitter:card as summary_large_image.
  • Use absolute URLs for og:image — relative paths will not be resolved by crawlers.
  • Image dimensions: 1200×630 px recommended (minimum 600×315 px).
  • Test with OGFixer after each deploy.

Preview your Qwik OG tags instantly

Paste any Qwik City URL into OGFixer to see live previews on Twitter, LinkedIn, Discord, and Slack — no login required.

Related Guides