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
- Use only `tagName: "meta"` or `tagName: "link"` for those entries
- For titles use `{ title: "..." }`; for scripts (JSON-LD, analytics) render a <script> in the route component or root layout instead of the meta export
- 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
- Type meta exports against MetaDescriptor (`satisfies MetaDescriptor[]`) so invalid tagName values fail at compile time
- Remember the meta export only supports meta/link/title/charset shapes — render <script> tags in components instead
- tagName is case-sensitive: always lowercase "meta"/"link"
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
- The route at ${_match.route.path} returns an invalid value.
- Prerender (data): Received a ${response.status} status code
- Module cannot have both a default export and a ServerCompone
- Module cannot have both a Layout export and a ServerLayout e
- Module cannot have both an ErrorBoundary export and a Server
AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18).
Data as JSON: /api/errors/f8982ce44183d23e.
Report an issue: GitHub.