How to Set Route Titles for Remix Apps

November 26, 2021
2 minute read

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">  
        <meta charSet="utf-8"/>  
        <meta name="viewport" content="width=device-width,initial-scale=1"/>  
        <!--Defines a default title below-->
        {title ? <title>{title}</title> : null}  
        {process.env.NODE_ENV === "development" && <LiveReload/>}

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: `${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.