cottage

How to Set Route Titles for Remix Apps

November 26, 2021
2 minute read
label

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.