OG Tags for React Native: How to Control Social Previews in Mobile Apps
React Native apps don't render HTML — here's how to control Open Graph previews when sharing deep links from your React Native app.
The fundamental problem: RN apps have no HTML
React Native renders to native UI components — not HTML. There is no <head> tag, no <meta property="og:image">, and no HTML document at all. When social platforms (Twitter, Slack, Discord, iMessage) fetch a shared URL to generate a preview card, they make an HTTP request and look for Open Graph meta tags in the response HTML. A React Native app cannot serve that HTML.
The key insight is: OG tags must live on your server or website, not in your mobile app. The mobile app shares a URL — that URL points to a web page (or API endpoint) that returns the correct HTML with OG meta tags.
How deep links and OG previews work together
When a user in your React Native app taps "Share", the app constructs a URL like https://yourapp.com/products/cool-sneakers. That URL is what gets shared. When the recipient's messaging app or social platform fetches that URL, it hits your web server — not your React Native app. Your web server must return HTML with the correct OG tags for that product.
This means you need a corresponding web route for every shareable deep link in your app.
Pattern 1: Next.js app with dynamic OG metadata
If you use Next.js for your web counterpart (common in React Native projects using Expo), create a web route that mirrors your app route and exports dynamic metadata:
// app/products/[slug]/page.tsx (Next.js)
import type { Metadata } from 'next';
interface Props {
params: { slug: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const product = await fetchProduct(params.slug);
return {
title: product.name,
description: product.tagline,
openGraph: {
title: product.name,
description: product.tagline,
images: [{
url: product.ogImage ?? `https://yourapp.com/og/products/${params.slug}.png`,
width: 1200,
height: 630,
}],
type: 'website',
url: `https://yourapp.com/products/${params.slug}`,
},
twitter: {
card: 'summary_large_image',
title: product.name,
description: product.tagline,
images: [product.ogImage ?? `https://yourapp.com/og/products/${params.slug}.png`],
},
};
}
export default function ProductPage({ params }: Props) {
// Optionally redirect mobile visitors to app via Universal Links
return <ProductPageContent slug={params.slug} />;
}Pattern 2: Express/Node.js API with OG HTML response
If your backend is Express, add a route that returns minimal HTML with OG tags for each shareable entity. Social crawlers get the OG tags; real users can be redirected to the app.
// routes/share.js (Express)
app.get('/share/product/:id', async (req, res) => {
const product = await Product.findById(req.params.id);
if (!product) return res.status(404).send('Not found');
const userAgent = req.headers['user-agent'] || '';
const isCrawler = /Twitterbot|LinkedInBot|Discordbot|Slackbot|facebookexternalhit/i.test(userAgent);
if (isCrawler) {
// Serve OG-optimized HTML for social crawlers
return res.send(`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${product.name}</title>
<meta property="og:type" content="product">
<meta property="og:title" content="${escapeHtml(product.name)}">
<meta property="og:description" content="${escapeHtml(product.tagline)}">
<meta property="og:image" content="${product.imageUrl}">
<meta property="og:url" content="https://yourapp.com/share/product/${product.id}">
<meta name="twitter:card" content="summary_large_image">
</head>
<body><p>Redirecting to app...</p></body>
</html>`);
}
// Redirect real users to the app or app store
const deepLink = `yourapp://products/${product.id}`;
res.redirect(302, deepLink);
});Note: User-agent sniffing for crawlers is a pragmatic approach, but be aware that not all platforms identify themselves accurately. The safest approach is to always serve OG-rich HTML at these URLs for all visitors.
Pattern 3: Firebase Dynamic Links (deprecated) / Branch.io
Deep linking services like Branch.io, Adjust, and AppsFlyer create intermediate URLs that automatically handle OG tags, fallback to web, and deep link routing across iOS and Android. They manage the OG metadata on their servers based on config you provide.
// Branch.io example: create a link with custom OG data
import branch from 'react-native-branch';
const linkProperties = {
feature: 'share',
channel: 'app',
};
const controlParams = {
$og_title: product.name,
$og_description: product.tagline,
$og_image_url: product.imageUrl,
$canonical_url: `https://yourapp.com/products/${product.id}`,
$desktop_url: `https://yourapp.com/products/${product.id}`,
$ios_deeplink_path: `products/${product.id}`,
$android_deeplink_path: `products/${product.id}`,
};
const { url } = await branch.createBranchUniversalObject(
`product/${product.id}`,
{ contentMetadata: { customMetadata: { product_id: product.id } } }
).generateShortUrl(linkProperties, controlParams);
Share.share({ url, message: product.tagline });Sharing content from React Native
Use React Native's built-in Share API to share your web URL. The URL you share is what social platforms will fetch for OG data.
import { Share } from 'react-native';
async function shareProduct(product) {
try {
await Share.share({
// This URL must have OG tags on the server side
url: `https://yourapp.com/products/${product.slug}`,
message: `Check out ${product.name} — ${product.tagline}`,
title: product.name,
});
} catch (error) {
console.error('Share failed:', error);
}
}Testing your OG tags from mobile
After setting up your server-side OG tags, test the shared URL using platform debuggers:
- Twitter Card Validator:
cards-dev.twitter.com/validator - Facebook Sharing Debugger:
developers.facebook.com/tools/debug - LinkedIn Post Inspector:
www.linkedin.com/post-inspector - Or paste into OGFixer for a unified preview across all platforms
Test your OG tags free
Paste any URL into OGFixer to see exactly how your link previews look on Twitter, LinkedIn, Discord, and Slack.