Metadata
Configure page titles, descriptions, Open Graph tags, Twitter cards, and more with a powerful metadata system. Define metadata statically or generate it dynamically based on route parameters.
Basic Metadata
Static Metadata
Export a metadata object from any page.tsx or layout.tsx file:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'Home | My App',
description: 'Welcome to my amazing application',
}
export default function HomePage() {
return <h1>Welcome</h1>
}The Metadata type provides full type safety and autocomplete for all metadata fields.
Dynamic Metadata
Use the generateMetadata function to create metadata based on route parameters:
import type { PageProps, Metadata } from 'rari'
export async function generateMetadata({ params }: PageProps<{ slug: string }>): Promise<Metadata> {
const post = await fetchPost(params.slug)
return {
title: ` | Blog`,
description: post.excerpt,
keywords: post.tags,
}
}
export default async function BlogPost({ params }: PageProps<{ slug: string }>) {
const post = await fetchPost(params.slug)
return <article>{post.content}</article>
}The PageProps type provides type-safe access to route parameters and search params, while the Metadata return type ensures your metadata object is correctly structured.
Metadata Fields
Title
The page title appears in browser tabs and search results.
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'About Us | My Company',
}Title Templates
Use title templates in layouts to automatically format child page titles:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: {
template: '%s | My App',
default: 'My App',
},
}import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'About Us', // Becomes "About Us | My App"
}Absolute Titles
Override the template with an absolute title:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: {
absolute: 'Special Page - No Template',
},
}Description
The meta description for SEO and social sharing:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'Products',
description: 'Browse our collection of high-quality products with fast shipping and great prices.',
}Keywords
Keywords for SEO (though less important for modern search engines):
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'React Tutorial',
description: 'Learn React from scratch',
keywords: ['react', 'tutorial', 'javascript', 'web development'],
}Canonical URL
Specify the canonical URL to avoid duplicate content issues:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'Blog Post',
canonical: 'https://example.com/blog/my-post',
}Robots
Control search engine crawling and indexing:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'Admin Dashboard',
robots: {
index: false,
follow: false,
nocache: true,
},
}Open Graph
Open Graph tags control how your pages appear when shared on social media platforms like Facebook, LinkedIn, and Slack.
Basic Open Graph
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My Blog Post',
description: 'An interesting article about web development',
openGraph: {
title: 'My Blog Post',
description: 'An interesting article about web development',
url: 'https://example.com/blog/my-post',
siteName: 'My Blog',
type: 'article',
},
}Open Graph Images
Simple Image URL
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'Product Page',
openGraph: {
title: 'Amazing Product',
description: 'Check out this amazing product',
images: ['https://example.com/images/product.jpg'],
},
}Detailed Image Configuration
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'Product Page',
openGraph: {
title: 'Amazing Product',
description: 'Check out this amazing product',
images: [
{
url: 'https://example.com/images/product.jpg',
width: 1200,
height: 630,
alt: 'Product photo',
},
],
},
}Dynamic Open Graph Images
Generate Open Graph images dynamically by creating an opengraph-image.tsx file:
import type { PageProps } from 'rari'
import { ImageResponse } from 'rari/og'
export default async function Image({ params }: PageProps<{ slug: string }>) {
const post = await fetchPost(params.slug)
return new ImageResponse(
<div
style={{
display: 'flex',
width: '100%',
height: '100%',
background: 'linear-gradient(to bottom right, #1e3a8a, #3b82f6)',
padding: '80px',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
>
<h1
style={{
fontSize: 72,
fontWeight: 'bold',
color: 'white',
marginBottom: 20,
}}
>
{post.title}
</h1>
<p
style={{
fontSize: 32,
color: '#e0e7ff',
}}
>
{post.author} • {post.date}
</p>
</div>
</div>,
)
}The image will be automatically injected into your page metadata and served at /_rari/og/blog/[slug].
Twitter Cards
Twitter cards control how your pages appear when shared on Twitter/X.
Summary Card
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My Article',
description: 'Read my latest article',
twitter: {
card: 'summary',
title: 'My Article',
description: 'Read my latest article',
site: '@mysite',
creator: '@myhandle',
},
}Summary Large Image Card
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My Article',
description: 'Read my latest article',
twitter: {
card: 'summary_large_image',
title: 'My Article',
description: 'Read my latest article',
site: '@mysite',
creator: '@myhandle',
images: ['https://example.com/images/article.jpg'],
},
}Icons and Favicons
Favicon
Place a favicon.ico file in your public/ directory, or specify it in metadata:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My App',
icons: {
icon: '/favicon.ico',
},
}Multiple Icon Sizes
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My App',
icons: {
icon: [
{ url: '/icon-16x16.png', sizes: '16x16', type: 'image/png' },
{ url: '/icon-32x32.png', sizes: '32x32', type: 'image/png' },
],
apple: '/apple-touch-icon.png',
},
}Viewport
Configure the viewport meta tag:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My App',
viewport: 'width=device-width, initial-scale=1, maximum-scale=5',
}Theme Color
Set the theme color for mobile browsers. You can use a simple string or an array with media queries:
Simple Theme Color
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My App',
themeColor: '#3b82f6',
}Theme Color with Media Queries
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My App',
themeColor: [
{ color: '#3b82f6', media: '(prefers-color-scheme: light)' },
{ color: '#1e3a8a', media: '(prefers-color-scheme: dark)' },
],
}Manifest
Link to a web app manifest:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My App',
manifest: '/manifest.json',
}Apple Web App
Configure Apple-specific web app metadata:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'My App',
appleWebApp: {
title: 'My App',
statusBarStyle: 'black-translucent',
capable: true,
},
}Complete Example
Here's a comprehensive example combining multiple metadata features:
import type { PageProps, Metadata } from 'rari'
export async function generateMetadata({ params }: PageProps<{ id: string }>): Promise<Metadata> {
const product = await fetchProduct(params.id)
return {
title: ` | Products`,
description: product.description,
keywords: product.tags,
openGraph: {
title: product.name,
description: product.description,
url: `https://example.com/products/`,
siteName: 'My Store',
type: 'website',
images: [
{
url: product.imageUrl,
width: 1200,
height: 630,
alt: product.name,
},
],
},
twitter: {
card: 'summary_large_image',
title: product.name,
description: product.description,
images: [product.imageUrl],
site: '@mystore',
creator: '@mystore',
},
robots: {
index: true,
follow: true,
},
canonical: `https://example.com/products/`,
}
}
export default async function ProductPage({ params }: PageProps<{ id: string }>) {
const product = await fetchProduct(params.id)
return (
<div>
<h1>{product.name}</h1>
<img src={product.imageUrl} alt={product.name} />
<p>{product.description}</p>
<p></p>
</div>
)
}Metadata Inheritance
Metadata is inherited from parent layouts and can be overridden by child pages:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: {
template: '%s | My App',
default: 'My App',
},
description: 'The best app ever',
openGraph: {
siteName: 'My App',
},
}import type { Metadata } from 'rari'
export const metadata: Metadata = {
openGraph: {
type: 'article',
},
}import type { Metadata } from 'rari'
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await fetchPost(params.slug)
return {
title: post.title, // Becomes "Post Title | My App"
description: post.excerpt, // Overrides parent description
// openGraph.siteName and openGraph.type are inherited
}
}Best Practices
Always Provide Title and Description
Every page should have a unique, descriptive title and description:
import type { Metadata } from 'rari'
// Good
export const metadata: Metadata = {
title: 'Contact Us | Acme Corp',
description: 'Get in touch with our team. We respond within 24 hours.',
}
// Bad
export const metadata: Metadata = {
title: 'Page',
}Use Title Templates
Define a title template in your root layout to maintain consistency:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: {
template: '%s | My Brand',
default: 'My Brand - Tagline',
},
}Optimize for Social Sharing
Always include Open Graph and Twitter card metadata for pages you expect to be shared:
import type { Metadata } from 'rari'
export const metadata: Metadata = {
title: 'Amazing Article',
description: 'This article will blow your mind',
openGraph: {
title: 'Amazing Article',
description: 'This article will blow your mind',
images: ['/og-image.jpg'],
},
twitter: {
card: 'summary_large_image',
title: 'Amazing Article',
description: 'This article will blow your mind',
images: ['/og-image.jpg'],
},
}Use Dynamic Metadata for Dynamic Routes
For pages with dynamic content, always use generateMetadata:
import type { Metadata } from 'rari'
// Good - Dynamic metadata
export async function generateMetadata({ params }): Promise<Metadata> {
const data = await fetchData(params.id)
return {
title: data.title,
description: data.description,
}
}
// Bad - Static metadata for dynamic route
export const metadata: Metadata = {
title: 'Item Page',
}Keep Descriptions Concise
Meta descriptions should be 150-160 characters for optimal display in search results:
import type { Metadata } from 'rari'
// Good - Concise and descriptive
export const metadata: Metadata = {
description: 'Learn React Server Components with rari. Fast, modern, and built on Rust for maximum performance.',
}
// Bad - Too long, will be truncated
export const metadata: Metadata = {
description: 'This is a very long description that goes on and on about various features and benefits without getting to the point quickly enough for search engines to display it properly in search results.',
}Related
- Image Component - Optimize images for better performance
- Open Graph Images - Generate dynamic social media images
- App Router - Learn about the app router structure