next.js icon indicating copy to clipboard operation
next.js copied to clipboard

next/head removing injected scripts

Open bmathews opened this issue 4 years ago • 23 comments

Bug report

Describe the bug

next/head is removing scripts added into head. This issue is currently causing the removal of scripts injected by our third-party tag manager.

To Reproduce

https://github.com/bmathews/nextjs-head-issue

Include a script in <Head /> that adds another script synchronously.

import Head from 'next/head';

const dynamicScript = `
  var s = document.createElement('script');
  s.innerHTML = "console.log('_index log')";
  document.head.appendChild(s);
`;

const Index = () => {
  return (
    <>
      <Head>
        <title>Index title</title>
        <script dangerouslySetInnerHTML={{ __html: dynamicScript }} />
        <meta name="theme-color" content="#ff6868"/>
      </Head>
      <div>Hello world</div>
    </>
  );
}

export default Index;

Expected behavior

next/head doesn't remove dynamically added scripts

Screenshots

I added "break on subtree modifications" to head. During hydration, before next/head performed removal, you can see the script console.log('_index log')

image

During removal, oldTags value in head-manager.js is containing that script:

image

After removal, console.log('_index log') is no longer there.

image

System information

  • OS: OSx
  • Browser (if applies): Only Safari, I think?
  • Version of Next.js: Tested 9.2, 9.3

bmathews avatar Mar 12 '20 19:03 bmathews

I'd like to bump this issue as it's causing a problem for us too. We have a 3rd party chat bot on our application that injects a script into the head. On initial load, the script is getting wiped since we are using <Head> in a base component. If the user refreshes the page, the chat bot then works since I assume the cached server-rendered page gets pulled and the client-side component then can successfully inject into the head without getting wiped.

mitchell-bu avatar Apr 03 '20 18:04 mitchell-bu

@mitchell-bu Don't know what the difference is tbh, but we got around the issue by moving our 3rd party script to _document.js's head rather than from a lower level component.

bmathews avatar Apr 03 '20 18:04 bmathews

Yea, not sure that's really going to be an option for us.

Interestingly, discovered that this was introduced in the 9.0.3 version. 9.0.2 doesn't have this problem.

mitchell-bu avatar Apr 03 '20 19:04 mitchell-bu

I'm fairly certain this is the PR that introduced this problem, which was opened by @devknoll : https://github.com/zeit/next.js/pull/8020/files Specifically the change to head-manager.js.

I frankly don't entirely follow what this is trying to do so I'm not really able to suggest a fix. We ultimately found a workaround using dynamic imports to delay importing the component that injects into the head until the client renders.

mitchell-bu avatar Apr 06 '20 15:04 mitchell-bu

Document is not parsed the same way. Document is SSR only, passing script tags through React does not work because the react parser does not accept scripts. The head manager does seem to have a cleanup step, but it does not seem to write scripts back to the page beyond what is flushed out of webpack initially.

ScriptedAlchemy avatar Jun 04 '20 21:06 ScriptedAlchemy

this problem was introduced by https://github.com/vercel/next.js/commit/e68307df3a1e4f1cc6da7b435517984f91aed8b3#diff-6d15c59f75bd4f8cdcbd29588c610545 to fix the SEO problems in https://github.com/vercel/next.js/issues/3494. that makes sense, however, the new implementation is tenuous. I think it's better to go back to a system where you have an explicit handle on which elements were added by Next. instead of classes, you could use data attributes. i have never seen an SEO problem with using those to manage HEAD tag reconciliation.

is it possible to offer reconciliation based on data attributes as an experimental feature? my team would be glad to try it out. i think it would resolve our issues and we would be able to validate that SEO is unaffected by data attributes.

wawjr3d avatar Jul 03 '20 15:07 wawjr3d

@wawjr3d solution sounds great. And based on documentation on data-attributes (https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes), the SEO crawler will not index values from data-attributes.

hilalarsa avatar Jul 24 '20 04:07 hilalarsa

have this issue too! commenting for exposure. was trying to add a script tag for tracking and was trying to figure out what was going on

michellewong793 avatar Aug 21 '20 19:08 michellewong793

Have a similar issue too, but for styles which are injected by another script of ours. Imagine could be the same root cause so wanted to add a comment here as it seems like not just <script> elements are affected.

arielsalminen avatar Aug 28 '20 11:08 arielsalminen

Have there been any updates on this issue? We've come across this problem while using third-party scripts on the Economist site. I've created a minimal repro app that shows the problem.

tyom avatar Feb 17 '21 12:02 tyom

It looks like we're unable to render structured data because of this problem.

Google Rich Results Test / googlebot sees the page without our implicit <script type="application/ld+json"> tag in the html and fails to mark the page eligible for displaying rich structured data results.

However this happens only when googlebot visits the page, not at all times.

kachar avatar Mar 03 '21 11:03 kachar

The scripts in next/head will execute, but they will execute after the components have rendered sometimes, resulting in missing globals. I had this issue with the Google Maps API specifically, and needed to guarantee this script had been fully executed before any components tired to render.

Workaround

  1. Override the document by creating a file called pages/_document.tsx as seen here.
  2. Add the <script> as a child to the <Head> element in the document.

Each page will now load your <script> before rendering, ensuring all globals are available and side effects have completed.

ctjlewis avatar Mar 25 '21 03:03 ctjlewis

Does this issue exist anymore? May someone share a codesandbox in that case?

rishi-raj-jain avatar Apr 24 '21 02:04 rishi-raj-jain

@rishi-raj-jain You can reproduce it with https://github.com/kachar/next-json-ld

It was extracted in #22729 but the core issue might be the same.

I haven't updated the repo in the last month so is with next=10.0.7 Do you think we should update to the latest version of Next ?

kachar avatar Apr 24 '21 13:04 kachar

Is there an update on this? For a project I've implemented Dynamic Yield which injects styles into the head that are overwritten. I've put the Dynamic Yield scripts right in the body before the content which solves the issue of the injected styles being overwritten but in turn affect how data is being shared with Dynamic Yield. The workaround provided in the comments here resulted in some other data sharing issues.

ChanelZM avatar Feb 28 '22 08:02 ChanelZM

This still seems to be an issue.

kaufmann42 avatar Mar 11 '22 18:03 kaufmann42

If anyone is stuck on this, you could monkey patch it, assuming the function is exported. Require.cache[require.resolve(file)].exports = your own code

ScriptedAlchemy avatar Mar 20 '22 08:03 ScriptedAlchemy

Google Tag Manager snippet code was inserted in my case at the end of other meta tags, which caused the same problem. The problem was solved after moving GTM snippet code to the top of meta tags.

KingMatrix1989 avatar Apr 04 '22 11:04 KingMatrix1989

any update?

kaufmann42 avatar Apr 12 '22 21:04 kaufmann42

I've had the same problem when adding content to the head, coming from a WordPress Seo Plugin(Yoast).

In my case, I was not adding a script, but a component where I was parsing this content. The content was not completely removed, but still partly shown in the head.

I could solve the problem by not using a component to parse the Seo head, but parsing it directly within the <Head> component.

I've reproduced the issue in the repo below. https://head-overwrite.vercel.app/ https://github.com/jeroenschieven/head-overwrite

jeroenschieven avatar Jun 02 '22 08:06 jeroenschieven

Related: #37747

I think it would require core maintainers to solve it.

aboqasem avatar Jun 21 '22 02:06 aboqasem

still an issue in 12.3.0

mp205 avatar Sep 16 '22 00:09 mp205

Not sure about next. But in webpack we inject the script then remove it to prevent memory leaks. If the script registers a global, does it matter if the Js tag is still there or not? Since it’s in memory

ScriptedAlchemy avatar Sep 16 '22 06:09 ScriptedAlchemy