SEO for Solos
7.xreact

React Router (Remix) SEO

Server-first by design, with metadata as a route export and headers as a route-level function. Prerendering is opt-in, which changes the static-first calculation.

What you start with

  • +A meta export per route, merged along the route hierarchy
  • +A headers export per route, so cache and security headers are route-scoped
  • +Loaders that run on the server by default

What it makes harder

  • !meta is not merged automatically in the way you expect: a child route replaces the parent's meta unless you spread it
  • !Server rendering is the default, so static-first requires the prerender config rather than being free
  • !The headers export is per route, so a sitewide security header needs the server entry or the host

Recipes

Each one is real React Router (Remix) code, not the same snippet with the imports swapped.

Canonical through the meta export

A relative canonical resolves differently depending on the requested URL, which is exactly the ambiguity a canonical exists to remove. Check meta-canonical-absolute

app/routes/pricing.tsx
import type { MetaFunction } from "react-router";

const SITE = "https://www.example.com";

export const meta: MetaFunction = ({ location }) => {
  const canonical = SITE + location.pathname.replace(/\/$/, "");
  return [
    { title: "Pricing | Acme" },
    { name: "description", content: "Three tiers, one-time payment." },
    { tagName: "link", rel: "canonical", href: canonical },
    { property: "og:url", content: canonical },
  ];
};

robots.txt as a resource route

A hand-maintained robots.txt drifts from the routes it is supposed to describe. Generating it means adding a private route section cannot forget to disallow it. Check crawl-robots-generated

app/routes/[robots.txt].tsx
import { robotsTxt, RECOMMENDED_POLICY } from "~/kit/seo/robots";

// The square brackets escape the dot, so the route is literally /robots.txt
// rather than a nested robots/txt path.
export function loader() {
  return new Response(
    robotsTxt({
      ...RECOMMENDED_POLICY,
      disallowPaths: ["/api/", "/account/"],
      sitemaps: ["https://www.example.com/sitemap.xml"],
    }),
    { headers: { "Content-Type": "text/plain; charset=utf-8" } },
  );
}

Security headers per route

Without it the first request of every session is plain http and redirectable. With preload the browser never makes that request at all. Check sec-hsts

app/root.tsx
import type { HeadersFunction } from "react-router";
import { securityHeaders } from "~/kit/config/headers";

// The headers export is per route. Setting it on root covers the tree, but a
// child route exporting its own headers replaces this rather than merging.
export const headers: HeadersFunction = () =>
  Object.fromEntries(securityHeaders().map((rule) => [rule.key, rule.value]));

Entity gate in the loader

An ungated dynamic route generates a page for any slug anyone types, including slugs an attacker generates. That is how a site accidentally publishes ten thousand thin pages. Check pseo-entity-gate

app/routes/glossary.$term.tsx
import type { LoaderFunctionArgs } from "react-router";
import { GLOSSARY_TERMS } from "~/data/glossary-terms";

export function loader({ params }: LoaderFunctionArgs) {
  const entry = GLOSSARY_TERMS.find((candidate) => candidate.slug === params.term);
  // Throwing a Response is how Remix signals a real status code from a loader.
  // Returning null would render the component with empty data: a soft 404.
  if (!entry) throw new Response("Not found", { status: 404 });
  return { entry };
}

The same checks in other frameworks

Questions

What does React Router (Remix) give you before you write any SEO code?

React Router (Remix) starts with: A meta export per route, merged along the route hierarchy; A headers export per route, so cache and security headers are route-scoped; Loaders that run on the server by default.

What does React Router (Remix) make harder?

meta is not merged automatically in the way you expect: a child route replaces the parent's meta unless you spread it This is specific to React Router (Remix) rather than a general SEO problem.

How many SEO recipes does SEO for Solos publish for React Router (Remix)?

SEO for Solos publishes 4 React Router (Remix) recipes, each with real React Router (Remix) code and a note on what differs from the same check in other frameworks.