Meta and links: SEO
Search engines and social networks read the tags in a page's head: title, meta name="description" and others. In framework mode each route module describes its tags with a meta export, and the Meta component in app/root.tsx renders them into HTML on the server. The links export adds link tags the same way.
Static tags
// app/routes/about.tsx
import type { Route } from "./+types/about";
export function meta({}: Route.MetaArgs) {
return [
{ title: "About the blog" },
{ name: "description", content: "A blog about React Router and web development." },
];
}Open /about — the browser tab is called "About the blog", and the page source has a description tag. meta returns an array of objects, each object is one tag.
Tags from data
The post title is known only after loading. meta receives the loader data in the data field:
// app/routes/post.tsx
export function meta({ data }: Route.MetaArgs) {
if (!data) {
return [{ title: "Post not found" }];
}
return [
{ title: data.post.title },
{ name: "description", content: data.post.body.slice(0, 150) },
{ property: "og:title", content: data.post.title },
];
}At /posts/1 the tab shows the post title, and when the link is shared in a messenger og:title is picked up. If the loader threw a 404, data is empty, so it is checked. In newer 7.x releases the same data is also available as loaderData.
Kinds of objects
| Object | Resulting tag |
|---|---|
{ title: "..." } | title |
{ name: "description", content: "..." } | meta name |
{ property: "og:image", content: "..." } | meta property |
{ tagName: "link", rel: "canonical", href: "..." } | link |
The links export
links returns an array of attributes for link tags: fonts, stylesheets, the icon:
// app/root.tsx
import type { Route } from "./+types/root";
export const links: Route.LinksFunction = () => [
{ rel: "icon", href: "/favicon.ico" },
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
];The Links component in head renders these tags. Links from a route's links are included only on its pages — so you can load a post's styles only where they are needed.
Common mistake
Expecting a parent route's tags to combine with a child's. Only the meta of the deepest matching route is rendered: if app/routes/post.tsx has no meta but the layout does, you get the layout's title; if both have one, only the post's title. That is why shared tags (charSet, viewport) are written directly in Layout in app/root.tsx.