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
headconstant 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.