Rendering Meta Tags with React Router Data APIs

React Router 7 (the evolution of Remix) ties metadata to its data layer: a loader fetches the route’s data on the server, and a meta export turns that data into title and meta tags rendered into the response. This keeps per-route metadata server-rendered and correct for crawlers, instead of being injected client-side after load. It is the data-API approach within React Router SEO best practices.

Step-by-step fix

  1. Load the route’s data in a loader. It runs on the server before rendering.

    // app/routes/products.$id.jsx
    export async function loader({ params }) {
      const product = await getProduct(params.id);
      return { product };
    }
  2. Return tags from the meta function using loader data.

    // ✅ Server-rendered, derived from loader data
    export function meta({ data }) {
      return [
        { title: `${data.product.name} — Example` },
        { name: 'description', content: data.product.summary },
        { property: 'og:title', content: data.product.name },
        { tagName: 'link', rel: 'canonical', href: `https://example.com/products/${data.product.id}` },
      ];
    }
  3. Avoid client-only meta injection for SEO-critical routes.

    // ❌ Runs only after hydration; crawlers may miss it
    useEffect(() => { document.title = product.name; }, [product]);
  4. Confirm server rendering is enabled so the meta export actually runs on the server and ships in the response.

Validation

  • curl of the route shows the title and meta tags in the HTML response.
  • View source (not just rendered DOM) contains the tags.
  • GSC URL Inspection rendered HTML matches the live head.
  • Hydration produces no mismatch warning for the head.

Reference

// app/routes/blog.$slug.jsx — loader + meta, server-rendered
export async function loader({ params }) {
  const post = await getPost(params.slug);
  if (!post) throw new Response('Not Found', { status: 404 });
  return { post };
}

export function meta({ data }) {
  if (!data?.post) return [{ title: 'Not Found' }];
  return [
    { title: data.post.title },
    { name: 'description', content: data.post.summary },
    { property: 'og:image', content: data.post.ogImage },
    { tagName: 'link', rel: 'canonical', href: `https://example.com/blog/${data.post.slug}` },
  ];
}

Frequently Asked Questions

Does the React Router meta export render on the server? In framework mode (React Router 7 / Remix) with server rendering enabled, the meta export runs on the server and its tags are in the initial HTML response. In a client-only SPA configuration, you need server rendering or prerendering for the tags to reach crawlers reliably.

How do I use loader data in the meta function? The meta function receives the route’s loader data as an argument, so you can return a title and description derived from it. Because the loader runs before rendering, the data is available when the meta tags are produced.

← Back to React Router SEO Best Practices