I ran into this error after updating Next.js to version 9.5.4. The problem is that you cannot use React Components inside Head
in _app.js file.
Minimal Example:
const Metadata = () => (
<>
<base href="/" />
<meta name="msapplication-TileColor" content="#000000" />
{/*...*/}
</>
)
class NextApp extends App {
render() {
return (
<>
<Head>
<title>Next Bug Report</title>
{/*Error*/}
<Metadata />
</Head>
</>
)
}
}
Just open the dev server
Reproduction repo: next-bug-report
Also having this issue when trying pass a component into the <Head>
There are also problems with adding metadata into the <Head>
on pages.
_pages/index.js:_
const HomePage = () => (
<>
<Head>
{/*Adding metadata doesn't work*/}
<title>Homepage</title>
<meta property="og:title" content="My new title" key="title"/>
</Head>
<div>Homepage</div>
</>
)
Screenshot:
Yeh this is a catch22 because we can't apply the open redirect security patch due to this major bug. Site doesn't function based on our use of scripts and other functionality that is required in the <Head>
and we rely on our metadata as a SEO-driven PWA.
Any suggestions on how to work around this issue until it's fixed? Here's an example of how we use it:
const HomePage = () => (
<>
<Head>
<CustomTitle /> // doesn't work
<ThirdPartyScript /> // doesn't work
</Head>
<div>Homepage</div>
</>
)
resulting in functionality issues per the runtime error. It "silently" fails in prod, meaning the page may still render depending on the Head implementation, but it completely kills dev environment per OP.
My understanding of the issue is this change and/or this change removed support for accepting react components as a type, whether said component is simply <title />
or an array of <meta />
and <link />
elements, it chokes as it always returns null
for that component type, which is a function:
Uncaught (in promise) DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('function CustomTitle() {
return __jsx("title", null, "HI");
}') is not a valid name.
at reactElementToDOM (webpack-internal:///./node_modules/next/dist/client/head-manager.js:20:21)
Running forked version of nextjs locally:
I wonder if the problem is the difference between what createElement
does with initialHeadEntries: HeadEntry[]
vs the default head: JSX.Element[]
... the latter is fine and worked but now it's serializing head
in a window object 馃
Upgrading to the last version we notice the same problem in our project, for example with
<Head>
<title>Page title</title>
... etc
<ResourceHints />
</Head>
ResourceHints
returns an array of react element.
if there the plan is remove array/react elements support in the head that change I think should be a major version, otherwise it's a bug, btw only happens us in dev mode
Also what is the purpose of having all the head tags attached to the global _NEXT_DATA_ object? In our case we have a large list of domains we prefetch, preconnect, to improve preformance and this list would increase the initial HTML of the page.
btw only happens us in dev mode
@vxcamiloxv at least for us, it happens in all environments (prod vs dev), it's just dev actually throws the error and kills dev environment (for us). Prod fails silently, still throwing an error in console, but it doesn't kill the site. If you look at your HTML markup, whatever was in your react component within <head>
is not injected.
According to one of the maintainers, it's by happy accident that this ever worked. Don't think they realized they were supporting this "feature" ;)
@jflayhart in our case we just called a function that returns an array of links
to prefetch
, preconnect
. so:
<Head>
<title>Page title</title>
... etc
{ getResourceHints() }
</Head>
instead of:
<Head>
<title>Page title</title>
... etc
<ResourceHints />
</Head>
That fixed our problem on dev build and removed the error on console in production. Maybe that could help, in some cases.
According to one of the maintainers, it's by happy accident that this ever worked. Don't think they realized they were supporting this "feature" ;)
And this is documented https://nextjs.org/docs/api-reference/next/head
@timneutkens according with the link or wrapped into maximum one level of <React.Fragment> or arrays...
so react components inside Head are supported according with that
I'm using the next-seo
(https://github.com/garmeeh/next-seo) project, and v9.5.4 breaks it entirely due to this issue. Should this plugin "never have worked" essentially? CC @garmeeh
so react components inside Head are supported according with that
It says this is supported:
<Head>
<>
<title>Hello World</title>
</>
</Head>
And this:
<Head>
{[
<title>Hello World</title>
]}
</Head>
I'm using the next-seo (garmeeh/next-seo) project, and v9.5.4 breaks it entirely due to this issue. Should this plugin "never have worked" essentially? CC @garmeeh
I'm saying it definitely did not work as you would expect on client-side navigation if it was rendered inside of next/head
.
However that is not how next-seo works afaik, it renders a list of tags, not a React component. And uses next/head
directly: https://github.com/garmeeh/next-seo/blob/337ca1f9d0932caef7d768f4c67a89aa08c28b59/src/meta/nextSEO.tsx#L24-L39
Just created a small app to verify what I've been saying:
import Head from "next/head";
function Test() {
return <title>Hello World</title>;
}
export default function Home() {
return (
<>
<Head>
<Test />
</Head>
<h1>Hello world!</h1>
</>
);
}
<title>
as part of the initial HTML response (can be observed in view-source)<title>
is immediately empty<Link>
to it you'll notice that <title>
is empty when navigating client-side as well, meaning the tags were not rendered.To further confirm this I went and added another tag as <title>
has a slight special case in next/head
. But the same behavior happens when you have <meta name="description" content="Description here" />
, it's part of the initial HTML but nowhere else, once the browser hydrates it's removed. On client-side navigation it's not rendered.
This was tested on [email protected]
. On 9.5.4
and later it indeed throws an error as the logic changed a bit to remove the need for next-head-count
which caused issues when external scripts were injecting tags (scripts, css etc) into the <head>
.
Overall we can bring the behavior that is currently there back, @jflayhart did some work for that in #17770. However that is not ideal obviously as you currently don't get <title>
etc when navigating client-side / after the initial page load.
Update: just checked the array list method (which next-seo uses) too and it seems that this has the same behavior as having a React component in 9.5.3
, in 9.5.4
and later it throws an error. That specific case we can definitely solve as it's just iterating over the array. I guess #17770 already covers that.
Yeh that makes alot of sense now, @timneutkens. Thanks for that example app.
However that is not ideal obviously as you currently don't get
etc when navigating client-side / after the initial page load.
Agreed, and I made changes to my PR based on your valid concerns. I hope my PR gets us to where we all want to be, especially now that I am less ignorant of the docs and more aware of the intentions behind next/head
.
I was discussing this with @devknoll (who opened #17920 after I talked to him). He made the very relevant point that if we fix the issue + render the head correctly on client-side navigation it'll actually further break apps that relied on the behavior we had in previous versions. E.g. what if they're injecting <script>
or <link>
tags that would suddenly get injected on client-side routing where they previously did not. Also useContext
and other React hooks that would be executed wrongly.
Even though not 100% ideal probably the best way to go would be to bring back the previous behavior and then introduce warnings at some point.
Even though not 100% ideal probably the best way to go would be to bring back the previous behavior and then introduce warnings at some point.
@timneutkens Makes sense. I tested https://github.com/vercel/next.js/pull/17920 and it filters out the null
stuff correctly, so that should get us where we were pre-9.5.4 (albeit still broken)! That would be great to get that released ASAP. So maybe https://github.com/vercel/next.js/pull/17770 could be a long-term fix for minor/major version roadmap with a "possible breaking change" callout in the changelog?
Although, I am confused with this statement here:
further break apps that relied on the behavior we had in previous versions
Are you saying that people have figured out that components in next/head
don't render across client-side navigation, so they're creating hacky workarounds that would break with this PR? It is very React to compose components like this, so I guess I fail to see how doing things right could mess things up if it's already broken. We are having the opposite issue as we thought components containing script tags _were_ being injected across client-side nav, but they aren't, so we are missing important script/meta tags!
EDIT: My point being if people are doing things the way React allows, this shouldn't be much of a breaking change, but I am likely missing something since I don't have as much insight into next/head.
Are you saying that people have figured out that components in
next/head
don't render across client-side navigation, so they're creating hacky workarounds that would break with this PR? I guess I fail to see how doing things right could mess things up if it's already broken. We are having the opposite issue as we thought components containing script tags _were_ being injected across client-side nav, but they weren't, so we were missing important script/meta tags!
The concern is that both are breaking changes. The next-head-count
causes a breakage for anyone who was using custom components. Adding client side rendering could then break anyone who was accidentally relying on those to render with context or not render on the client.
I think we should fix it but:
There鈥檚 still a problem that #17920 doesn鈥檛 fix: non-title elements rendered by custom components will be orphaned, where previously they would just be deleted when Next initialized.
I鈥檓 not sure yet how that should be addressed.
I'm using the next-seo (garmeeh/next-seo) project, and v9.5.4 breaks it entirely due to this issue. Should this plugin "never have worked" essentially? CC @garmeeh
I'm saying it definitely did not work as you would expect on client-side navigation if it was rendered inside of next/head.
However that is not how next-seo works afaik, it renders a list of tags, not a React component. And uses next/head directly: https://github.com/garmeeh/next-seo/blob/337ca1f9d0932caef7d768f4c67a89aa08c28b59/src/meta/nextSEO.tsx#L24-L39
Closing the loop here for some that may come across this thread, I was incorrectly using <NextSeo>
nested in <Head>
, which threw this error.
Bad, also breaks on 9.5.4
<>
<Head>
<NextSeo {...} />
</Head>
<main>
<MyApp {...} />
</main>
</>
Good, works on 9.5.4
<>
<NextSeo {...} />
<main>
<MyApp {...} />
</main>
</>
For our e-commerce app we were lucky since we were already using stateless, functional components within Head
. For example, we simply went from JSX "component" syntax, <ThirdPartyScripts />
, to functional syntax {ThirdPartyScripts()}
and all is well >=9.5.4.
鈩癸笍 As the docs say, you MUST have it as direct children of Head, or wrapped at maximum in one level of <React.Fragment>
or arrays. Admittedly, I was just use to React, so I threw components in Head without reading that part of the docs 馃榿
I think it's safe to say this won't be supported soon (as it officially never was), but the maintainers are aware it's desired and I bet it will be supported in some major version based on breaking changes. For now, functions that return Fragment or array work! Sorry to anyone who has to refactor their class components, but at the same time it's only some meta/link tags.
P.S. At this point, @timneutkens I think we can probably call this out as a leaky bug, but hopefully support this as a react-ish feature eventually. You all will decide and make the right decision, thanks!
@timneutkens But how come next/document
"Head" works, but next/head
doesn't? We are having no problems with JSX component syntax in our custom _document
head:
// _document.tsx
import { Head } from 'next/document'
<Head>
<SomeScript />
<AnotherOne />
</Head>
EDIT: I guess the docs are saying it is literally rendered as raw head html, so it works seamlessly with JSX?
The
<Head />
component used here is not the same one from next/head. The<Head />
component used here should only be used for any<head>
code that is common for all pages. For all other cases, such as<title>
tags, we recommend using next/head in your pages or components.
My my... can learn a lot from re-reading documentation 馃槈
@timneutkens But how come
next/document
"Head" works, butnext/head
doesn't? I don't see any problems with JSX component syntax in our custom_document
head.
Because pages/_document
is just a shell for the page and is never rendered client-side so there's no hydration involved to ensure it is kept up-to-date between page changes. We can't tell React to render into only a part of the <head>
, only the full <head>
which would throw away anything that was currently there. Hence why next/head
has custom hydration bit that knows how to render React elements (which is what JSX compiles to)
As another call out, for any of those who use a custom server, you could just write some express middleware to fix the security issue to properly sanitize or 404 the open redirect bug. Just saying so no one feels 100% stuck between a rock and a hard place.
For me this only occurred while using Next-Seo library like this:
<Head>
{withBreadCrumbs && (
<BreadcrumbJsonLd itemListElements={breadCrumbs} />
)}
</Head>
But now taking it out from the Head element, it works while still remaining inside the page head.
{withBreadCrumbs && (
<BreadcrumbJsonLd itemListElements={breadCrumbs} />
)}
I guess this work since BreadcrumbJsonLd returns a
Most helpful comment
Also having this issue when trying pass a component into the
<Head>