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:

src/app/page.tsx
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:

src/app/blog/[slug]/page.tsx
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:

src/app/layout.tsx
import type { Metadata } from 'rari'

export const metadata: Metadata = {
  title: {
    template: '%s | My App',
    default: 'My App',
  },
}
src/app/about/page.tsx
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:

src/app/blog/[slug]/opengraph-image.tsx
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:

src/app/products/[id]/page.tsx
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:

src/app/layout.tsx
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',
  },
}
src/app/blog/layout.tsx
import type { Metadata } from 'rari'

export const metadata: Metadata = {
  openGraph: {
    type: 'article',
  },
}
src/app/blog/[slug]/page.tsx
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:

src/app/layout.tsx
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.',
}