remix-run/react-router · warning

A meta object uses an invalid tagName: ${tagName}. Expected

Error message

A meta object uses an invalid tagName: ${tagName}. Expected either 'link' or 'meta'

What it means

When the <Meta> component renders a route's `meta` export, entries carrying a `tagName` key are validated by isValidMetaTag, which only accepts "meta" or "link". Any other tag (e.g., "script", "base", or a typo like "Meta") logs this warning and that entry renders null — the page still renders, just without that head tag. Titles, charSet, and property/name-based entries have their own dedicated shapes and don't use tagName.

Source

Thrown at packages/react-router/lib/dom/ssr/components.tsx:687

    }

    match.meta = routeMeta;
    matches[i] = match;
    meta = [...routeMeta];
    leafMeta = meta;
  }

  return (
    <>
      {meta.flat().map((metaProps) => {
        if (!metaProps) {
          return null;
        }

        if ("tagName" in metaProps) {
          let { tagName, ...rest } = metaProps;
          if (!isValidMetaTag(tagName)) {
            console.warn(
              `A meta object uses an invalid tagName: ${tagName}. Expected either 'link' or 'meta'`,
            );
            return null;
          }
          let Comp = tagName;
          return <Comp key={JSON.stringify(rest)} {...rest} />;
        }

        if ("title" in metaProps) {
          return <title key="title">{String(metaProps.title)}</title>;
        }

        if ("charset" in metaProps) {
          metaProps.charSet ??= metaProps.charset;
          delete metaProps.charset;
        }

        if ("charSet" in metaProps && metaProps.charSet != null) {

View on GitHub (pinned to 6beaca3952)

Solutions

  1. Use only `tagName: "meta"` or `tagName: "link"` for those entries
  2. For titles use `{ title: "..." }`; for scripts (JSON-LD, analytics) render a <script> in the route component or root layout instead of the meta export
  3. Check casing — "Meta"/"Link" are invalid; only lowercase passes

Example fix

// before - app/routes/product.tsx
export const meta = () => [
  { tagName: "script", type: "application/ld+json", children: JSON.stringify(ld) },
];

// after
export const meta = () => [{ title: "Product" }];
// in the route component / root layout:
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />
Defensive patterns

Strategy: type-guard

Validate before calling

// filter meta entries before returning them from a route's `meta` export
function safeMeta(entries) {
  return entries.filter((e) => !e?.tagName || e.tagName === "meta" || e.tagName === "link");
}
export const meta = () => safeMeta([{ tagName: "script", rel: "x" }, { title: "OK" }]);

Type guard

type MetaTagName = "meta" | "link";
function isValidMetaTag(tagName: unknown): tagName is MetaTagName {
  return tagName === "meta" || tagName === "link";
}
// usage: if ("tagName" in entry && !isValidMetaTag(entry.tagName)) drop(entry);

Prevention

When it happens

Trigger: A route module's `meta` function (or array) returns an object like `{ tagName: "script", type: "application/ld+json", ... }` or any tagName other than exactly "meta"/"link" — note the check is case-sensitive.

Common situations: Trying to inject JSON-LD/structured data or analytics scripts through the meta export; casing typos ("Meta", "Link"); assuming meta supports arbitrary head tags because it returns objects.

Related errors


AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18). Data as JSON: /api/errors/f8982ce44183d23e. Report an issue: GitHub.