Skip to content

feat(eslint-plugin): add no-unallowed-tags-in-head rule#94081

Open
AliMahmoudDev wants to merge 3 commits into
vercel:canaryfrom
AliMahmoudDev:fix/no-unallowed-tags-in-head
Open

feat(eslint-plugin): add no-unallowed-tags-in-head rule#94081
AliMahmoudDev wants to merge 3 commits into
vercel:canaryfrom
AliMahmoudDev:fix/no-unallowed-tags-in-head

Conversation

@AliMahmoudDev
Copy link
Copy Markdown

What?

Adds a new ESLint rule no-unallowed-tags-in-head that detects and warns when invalid HTML elements (e.g., <html>, <body>, <div>, <main>, <section>) are used as children of <Head> from next/head.

This prevents the misleading "next-head-count is missing" error at its root cause by catching invalid tags at lint time.

Before (runtime error):

Warning: next-head-count is missing. https://nextjs.org/docs/messages/next-head-count-missing

After (lint warning):

Do not use `<html>` inside `<Head>` from `next/head`. Only tags valid in `<head>` are allowed: `title`, `meta`, `link`, `style`, `script`, `base`, `noscript`, and `template`. See: https://nextjs.org/docs/messages/no-unallowed-tags-in-head

Why?

When invalid HTML elements are placed inside <Head>, the browser silently moves them (and all subsequent content) from <head> to <body>. This causes the next-head-count meta tag to be displaced, resulting in a confusing error message that does not point to the actual cause. Issue: #20924

As suggested by @timneutkens, this should be an ESLint rule rather than a runtime fix.

How?

  • Created packages/eslint-plugin-next/src/rules/no-unallowed-tags-in-head.ts
  • Registered the rule in packages/eslint-plugin-next/src/index.ts (added to recommended)
  • Added comprehensive test suite in test/unit/eslint-plugin-next/no-unallowed-tags-in-head.test.ts
  • Added error documentation in errors/no-unallowed-tags-in-head.mdx

The rule:

  • Only triggers when next/head is imported (avoids false positives)
  • Allows React.Fragment wrapping (1 level deep, matching Next.js behavior)
  • Skips custom React components (PascalCase) that may render valid head tags internally
  • Reports on lowercase HTML tags that are not in the valid <head> tag set
  • Set to warn in the recommended config

Fixes #20924

When invalid HTML elements like <html>, <body>, <div>, etc. are used
inside next/head, the browser silently moves them to <body>, causing
all subsequent head tags to be displaced. This previously resulted in
the misleading 'next-head-count is missing' error.

This new ESLint rule detects and warns about invalid HTML tags used
as children of the <Head> component from next/head, providing a clear
and actionable error message at lint time.

Only tags valid inside HTML <head> are allowed: title, meta, link,
style, script, base, noscript, and template. Custom React components
(PascalCase) and React.Fragment are allowed.

Fixes vercel#20924
Comment thread packages/eslint-plugin-next/src/rules/no-unallowed-tags-in-head.ts
Comment thread packages/eslint-plugin-next/src/rules/no-unallowed-tags-in-head.ts Outdated
…horthand

- Use 'JSXIdentifier' AST node type instead of 'Identifier' for JSX
  tag name checks. In the ESTree AST used by ESLint, JSX tag names
  have type 'JSXIdentifier', not 'Identifier'.
- Move JSXFragment shorthand (<>...</>) handling outside of the
  JSXElement check so that fragments are properly traversed and their
  children are checked for invalid tags.
- Keep named Fragment (<Fragment>, <React.Fragment>) handling inside
  JSXElement since those have a JSXIdentifier name.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Misleading "next-head-count is missing" error for invalid head tags

1 participant