How to Set Route Titles for Remix Apps
Remix is a pretty cool full-stack web framework that recently ditched their paid licensing model. It allows developers to create modern JS/TS web apps with server-side rendering, no load states, and their pièce de résistance - nested routing. Some of you may have already noticed that this blog is built with Remix (and Cloudflare Workers) and I have to say that I missed the simplicity of building a web app without any XHR or fetch()
API calls like back in the PHP days.
Something that I ran into while building this blog was the question of how to set the document title based on the current post. Initially, I had some difficulty navigating the official documentation for guidance on this subject. After looking into it for a bit, it turns out that it’s actually really easy to set all sorts of <meta>
tags based on the current (even nested) route.
The docs groups setting document titles with <meta>
tags which was why I didn’t find it at first glance. It also doesn’t mention the {data}
property for some reason so here’s a quick reference for how everything works.
Remix has a <Meta/>
component that you can place in the <head>
of your Document function.
<html lang="en">
<head>
<meta charSet="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<Meta/>
<!--Defines a default title below-->
{title ? <title>{title}</title> : null}
<Links/>
</head>
<body>
{children}
<RouteChangeAnnouncement/>
<ScrollRestoration/>
<Scripts/>
{process.env.NODE_ENV === "development" && <LiveReload/>}
</body>
</html>
Note: If you create your project with npx create-remix
, the <Meta/>
component is placed after the <title>
tag, meaning any title you set on your <Document/>
will not be overridden by the <Meta/>
component.
Once your <Meta/>
is properly placed, you can now export a meta()
from each route you want to have variable <meta>
tags.
import invariant from "tiny-invariant";
export let meta: MetaFunction = ({data}) => {
invariant(data, "expected data");
return {
title: data.title,
description: data.short,
url: `https://blog.teemaw.dev/posts/${data.slug}`,
type: "article",
date: data.published,
tags: data.tags,
}
};
This function will take whatever is returned by your loader()
as data
and return it to your <Meta/>
component which then populates your document with actual HTML <meta>
tags.
If you have nested routes, the inner-most route will be used to populate your document’s <meta>
tags.