Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failed to execute 'createElement' on 'Document' | v9.5.4 #17721

Closed
ghost opened this issue Oct 8, 2020 · 28 comments · Fixed by #18713
Closed

Failed to execute 'createElement' on 'Document' | v9.5.4 #17721

ghost opened this issue Oct 8, 2020 · 28 comments · Fixed by #18713
Assignees
Milestone

Comments

@ghost
Copy link

ghost commented Oct 8, 2020

Bug report

Describe the bug

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>
          </>
      )
  }
}

To Reproduce

Just open the dev server

Reproduction repo: next-bug-report

Screenshots

image

System information

  • Version of Next.js: [9.5.4]
  • Version of React/ReactDOM: [16.13.1]
  • Version of Node.js: [14.0.0]
@ghost ghost changed the title Failed to execute 'createElement' on 'Document' on "v9.5.4" Failed to execute 'createElement' on 'Document' on 'v9.5.4' Oct 8, 2020
@ghost ghost changed the title Failed to execute 'createElement' on 'Document' on 'v9.5.4' Failed to execute 'createElement' on 'Document' | v9.5.4 Oct 8, 2020
@danvoyce
Copy link

danvoyce commented Oct 9, 2020

Also having this issue when trying pass a component into the <Head>

@ghost ghost closed this as completed Oct 9, 2020
@ghost ghost reopened this Oct 9, 2020
@jflayhart
Copy link
Contributor

jflayhart commented Oct 9, 2020

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.

@jflayhart
Copy link
Contributor

jflayhart commented Oct 9, 2020

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:
Screen Shot 2020-10-09 at 5 00 16 PM

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 🤔
Screen Shot 2020-10-09 at 5 23 11 PM

@ghost
Copy link

ghost commented Oct 14, 2020

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

@alaingoga
Copy link

alaingoga commented Oct 14, 2020

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.

image

@jflayhart
Copy link
Contributor

jflayhart commented Oct 14, 2020

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.

@jflayhart
Copy link
Contributor

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" ;)

@alaingoga
Copy link

alaingoga commented Oct 14, 2020

@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.

@timneutkens
Copy link
Member

timneutkens commented Oct 15, 2020

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

@ghost
Copy link

ghost commented Oct 15, 2020

@timneutkens according with the link or wrapped into maximum one level of <React.Fragment> or arrays...
screenshot-2020-10-15_10-50-20

so react components inside Head are supported according with that

@schlosser
Copy link

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

@timneutkens
Copy link
Member

timneutkens commented Oct 15, 2020

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

@timneutkens
Copy link
Member

timneutkens commented Oct 15, 2020

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>
    </>
  );
}
  • Renders <title> as part of the initial HTML response (can be observed in view-source)
  • Once hydrated <title> is immediately empty
  • If you create another page and <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 next@9.5.3. 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.

@jflayhart
Copy link
Contributor

jflayhart commented Oct 15, 2020

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 <title> 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.

@timneutkens
Copy link
Member

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.

@jflayhart
Copy link
Contributor

jflayhart commented Oct 16, 2020

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 #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 #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.

@devknoll
Copy link
Contributor

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:

  1. We should find a way to make the client side render isomorphic
  2. It should be done in a major or backward compatible/opt-in way so we don’t break upgrading

@devknoll
Copy link
Contributor

There’s still a problem that #17920 doesn’t fix: non-title elements rendered by custom components will be orphaned, where previously they would just be deleted when Next initialized.

I’m not sure yet how that should be addressed.

@schlosser
Copy link

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>
</>

@jflayhart
Copy link
Contributor

jflayhart commented Oct 17, 2020

For our e-commerce app we were lucky since we were already using stateless, functional components within Head. For example, we simply changed 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!

@jflayhart
Copy link
Contributor

jflayhart commented Oct 17, 2020

@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
Copy link
Member

@timneutkens But how come next/document "Head" works, but next/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)

@jflayhart
Copy link
Contributor

jflayhart commented Oct 17, 2020

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.

@omar-dulaimi
Copy link

omar-dulaimi commented Oct 18, 2020

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 <script> element and not a Head. I could be wrong.

@matfin
Copy link

matfin commented Oct 19, 2020

I had the same issue as described above with my photography website and was able to make a workaround fix thing for my use case.

The PR I created is here and this contains a diff of the changes.

A description of what I did is as follows:

  • I had the next/head import and <Head /> component in my Layout component, and in there, I imported my Meta component.

  • Because the aforementioned <Meta /> component contained a React fragment <></> and a load of html meta tags, this caused the error because they were not directly nested under the <Head /> component.

  • The fix was to bring the <Head /> component into the <Meta /> component and the html meta tags could be direct children.

  • Fixing the Meta component tests was quite tricky, because the children inside <Head /> would not render, so I had to mock the <Head /> component using jest.mock('next/head').

After all that, I was able to get things working and reach full test coverage once again.

Hope this helps someone else. An interesting challenge to solve!

@osdevisnot
Copy link

I assume this was introduced in #16758 and also impacts #17854

@bencodesall
Copy link

bencodesall commented Nov 15, 2020

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!

@jflayhart 100%! I was struggling with this and switching to calling as "functional" {Thing()} instead of component <Thing /> along with wrapping JSX return in <React.Fragment> solved all my issues.

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.