Page Metadata

Retend's router supports page metadata such as titles, descriptions, Open Graph tags, and Twitter Cards. Metadata is defined per route and updates when navigation occurs. With SSR or SSG, the metadata is included in the initial HTML head.

Setting a Page Title

Set the page title with the title property on a route definition:

const routes = defineRoutes([
  {
    path: '/',
    component: Home,
    title: 'Home | My App',
  },
  {
    path: '/about',
    component: About,
    title: 'About Us | My App',
  },
]);

When navigation reaches /about, the browser tab title becomes "About Us | My App".

Route Metadata

For descriptions and social sharing tags, use the metadata property:

const routes = defineRoutes([
  {
    path: '/',
    component: Home,
    metadata: {
      title: 'Home | My App',
      description: 'Welcome to My App.',
      ogTitle: 'My App',
      ogImage: 'https://example.com/og-image.png',
    },
  },
]);

Available Properties

Retend recognizes these metadata keys and maps them to the appropriate HTML tags:

KeyHTML Output
title<title>
description<meta name="description">
author<meta name="author">
keywords<meta name="keywords">
viewport<meta name="viewport">
lang<html lang="...">
charset<meta charset="...">
themeColor<meta name="theme-color">

Open Graph (for Facebook, LinkedIn, etc.):

KeyHTML Output
ogTitle<meta property="og:title">
ogDescription<meta property="og:description">
ogImage<meta property="og:image">
ogUrl<meta property="og:url">
ogType<meta property="og:type">
ogLocale<meta property="og:locale">
ogSiteName<meta property="og:site_name">

Twitter Cards:

KeyHTML Output
twitterCard<meta name="twitter:card">
twitterTitle<meta name="twitter:title">
twitterDescription<meta name="twitter:description">
twitterImage<meta name="twitter:image">

Dynamic Metadata

To derive metadata from the current URL, pass a function instead of an object. For example, a user profile page can use the user's name in its title:

const routes = defineRoutes([
  {
    path: '/users/:userId',
    component: UserProfile,
    metadata: async ({ params }) => {
      const userId = params.get('userId');
      const user = await fetchUser(userId);
      return {
        title: `${user.name} | My App`,
        description: user.bio,
        ogTitle: user.name,
        ogImage: user.avatar,
      };
    },
  },
]);

The function receives an object with:

  • params: A Map<string, string> of matched URL parameters.
  • query: A URLSearchParams of the query string.

Component-Level Metadata

Metadata can also be attached directly to a component function:

function BlogPost(props) {
  return <article>...</article>;
}

BlogPost.metadata = async ({ params }) => {
  const post = await fetchPost(params.get('slug'));
  return {
    title: post.title,
    description: post.excerpt,
  };
};

This places metadata with the component that uses it. If both the route and component define metadata, the values are merged and component metadata takes precedence.

Metadata Cascading

In nested routes, metadata accumulates from parent to child. Child routes override parent values for the same key:

const routes = defineRoutes([
  {
    path: '/',
    component: Layout,
    metadata: {
      ogSiteName: 'My App',
      lang: 'en',
    },
    children: [
      {
        path: 'blog',
        component: BlogList,
        metadata: {
          title: 'Blog | My App',
          description: 'Read our latest posts.',
        },
      },
    ],
  },
]);

When the user visits /blog, the final metadata combines both levels:

  • ogSiteName: "My App" (from parent)
  • lang: "en" (from parent)
  • title: "Blog | My App" (from child)
  • description: "Read our latest posts." (from child)