Expo Open Graph Meta Tags: Control Social Previews for Expo Web and Deep Links
Expo apps targeting the web with Expo Router or expo-web need OG tags served from server-rendered HTML — here's the exact setup for og:title, og:image, og:description, and Twitter cards.
OG tags in Expo Web (Expo Router v3)
Expo Router v3 brings file-based routing to React Native and Expo Web. When you target the web platform, Expo Router supports static export and server-side rendering — both of which can embed OG meta tags in the HTML before it reaches the browser.
Social crawlers (Twitterbot, LinkedInBot, Discordbot) don't run JavaScript. For your Expo Web app to have working social previews, OG tags must be present in the raw HTML response. Expo Router's expo-router/head module provides the API to do this.
First, make sure your app.json targets the web platform and has the web output configured:
// app.json
{
"expo": {
"name": "My App",
"platforms": ["ios", "android", "web"],
"web": {
"bundler": "metro",
"output": "static"
}
}
}expo-router/head for per-screen OG
Use the <Head> component from expo-router/head to set OG meta tags per screen. During static export and SSR, Expo Router captures these tags and embeds them in the HTML.
// app/index.tsx (home screen)
import { Head } from 'expo-router/head';
import { View, Text } from 'react-native';
export default function HomeScreen() {
return (
<>
<Head>
<title>My App — Build Cross-Platform Apps</title>
<meta name="description" content="A cross-platform app built with Expo." />
<meta property="og:type" content="website" />
<meta property="og:title" content="My App — Build Cross-Platform Apps" />
<meta property="og:description" content="A cross-platform app built with Expo." />
<meta property="og:image" content="https://yourdomain.com/og/home.png" />
<meta property="og:url" content="https://yourdomain.com/" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="My App — Build Cross-Platform Apps" />
<meta name="twitter:description" content="A cross-platform app built with Expo." />
<meta name="twitter:image" content="https://yourdomain.com/og/home.png" />
</Head>
<View>
<Text>Welcome to My App</Text>
</View>
</>
);
}For dynamic screens (blog posts, user profiles), fetch data and inject it into the <Head> component. With output: "server", these tags render server-side. With output: "static", they are embedded at build time for known routes.
// app/blog/[slug].tsx
import { Head } from 'expo-router/head';
import { useLocalSearchParams } from 'expo-router';
import { useEffect, useState } from 'react';
export default function BlogPostScreen() {
const { slug } = useLocalSearchParams<{ slug: string }>();
const [post, setPost] = useState<Post | null>(null);
useEffect(() => {
fetchPost(slug).then(setPost);
}, [slug]);
return (
<>
{post && (
<Head>
<title>{post.title} | My Blog</title>
<meta property="og:type" content="article" />
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
<meta property="og:image" content={post.ogImage} />
<meta property="og:url" content={`https://yourdomain.com/blog/${slug}`} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={post.ogImage} />
</Head>
)}
{/* screen content */}
</>
);
}Deep link previews in native
When users share deep links from your native Expo app (e.g., myapp://blog/post-slug), messaging apps like iMessage and WhatsApp try to preview the link. Native deep links don't serve HTML, so they can't have OG tags directly.
The best practice is to use universal links (iOS) and App Links (Android) that map to real web URLs. Your Expo Web app at https://yourdomain.com/blog/post-slug serves the OG tags, and the OS redirects to the native app if installed. This gives you both deep link behavior and rich social previews.
Static export OG tags
When using output: "static", Expo Router generates static HTML for each known route at build time. For dynamic routes, you can pre-generate a list of paths:
// app/blog/[slug]+api.tsx (generates static paths)
export async function generateStaticParams() {
const posts = await fetchAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
// Each generated page includes the Head tags
// from your screen component, baked into static HTMLThe generated HTML files in the dist/ folder include all OG meta tags from your <Head> components — ready to be served from a CDN or static host.
OG tag checklist for Expo
- Add
expo-router/headto every screen that needs social preview support. - Use
output: "server"for fully dynamic SSR OG tags, or"static"for build-time generation. - 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(1200×630 px). - For deep links, set up universal links / App Links to map native URLs to web URLs.
Test your Expo Web OG tags
Paste your Expo Web URL into OGFixer to see previews on Twitter, LinkedIn, Discord, and Slack — no account needed.