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

next/head removing injected scripts #11012

Closed
bmathews opened this issue Mar 12, 2020 · 31 comments · Fixed by #16758 or #47938
Closed

next/head removing injected scripts #11012

bmathews opened this issue Mar 12, 2020 · 31 comments · Fixed by #16758 or #47938
Assignees
Labels
area: Metadata Metadata (metadata, generateMetadata, next/head, head.js) good first issue Easy to fix issues, good for newcomers kind: bug Confirmed bug that is on the backlog type: needs investigation

Comments

@bmathews
Copy link
Contributor

bmathews commented Mar 12, 2020

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
@Timer Timer added this to the 9.3.1 milestone Mar 13, 2020
@Timer Timer modified the milestones: 9.3.1, 9.3.2 Mar 13, 2020
@Timer Timer modified the milestones: 9.3.2, 9.3.3, 9.3.4 Mar 27, 2020
@mitchell-bu
Copy link

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

@bmathews
Copy link
Contributor Author

bmathews commented Apr 3, 2020

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

@mitchell-bu
Copy link

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.

@Timer Timer modified the milestones: 9.3.4, 9.3.5 Apr 3, 2020
@mitchell-bu
Copy link

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.

@Timer Timer modified the milestones: 9.3.5, 9.3.6 Apr 15, 2020
@Timer Timer modified the milestones: 9.3.6, 9.3.x Apr 28, 2020
@ScriptedAlchemy
Copy link
Contributor

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.

@wawjr3d
Copy link
Contributor

wawjr3d commented Jul 3, 2020

this problem was introduced by e68307d#diff-6d15c59f75bd4f8cdcbd29588c610545 to fix the SEO problems in #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.

@hilalarsa
Copy link

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

@Timer Timer added the kind: bug Confirmed bug that is on the backlog label Jul 27, 2020
@Timer Timer modified the milestones: 9.x.x, backlog Aug 18, 2020
@michellewong793
Copy link

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

@arielsalminen
Copy link

arielsalminen commented Aug 28, 2020

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.

@Timer Timer added the good first issue Easy to fix issues, good for newcomers label Aug 30, 2020
@kodiakhq kodiakhq bot closed this as completed in #16758 Sep 9, 2020
@ChanelZM
Copy link

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.

@kaufmann42
Copy link

This still seems to be an issue.

@ScriptedAlchemy
Copy link
Contributor

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

@KingMatrix1989
Copy link

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.

@kaufmann42

This comment was marked as off-topic.

@jeroenschieven
Copy link

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

@aboqasem
Copy link

Related: #37747

I think it would require core maintainers to solve it.

@mp205
Copy link

mp205 commented Sep 16, 2022

still an issue in 12.3.0

@ScriptedAlchemy
Copy link
Contributor

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

@rlarner-quizlet
Copy link

We ultimately found a workaround using dynamic imports to delay importing the component that injects into the head until the client renders.

@mitchell-bu mind sharing the details of your dynamic import work-around? I am also encountering this with a <script> that adds a third-party script to the <head> during SSR, but it's stripped when the client takes over. I tried moving it to a dynamic import, but now the script is not added - I suspect delaying the business means the script doesn't execute as it did before.

@rlarner-quizlet
Copy link

rlarner-quizlet commented Nov 9, 2022

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.

thanks for this tip @skmohammadi! In my case the third-party script was being inserted before the first <script> in the <head> and was getting wiped out. When I adjusted it to go before the first <meta> tag instead it stuck around.

@JurajKavka
Copy link

I must confirm that issue. We have third party script that dynamically inserts <script src="" id=""> tag to the head. During SPA transitions script is wiped out and added which is not desirable. Script should not be wiped out during SPA route change.

@vafada
Copy link

vafada commented Dec 9, 2022

we see this issue with StencilJS injected <style> tag... on a React change state, the injected <style> tag disappears

@LukasBombach
Copy link

I understand what happens as follows:

  • Developers get to use the <Head /> component from next/head to write stuff to the actual <head /> tag
  • You can use <Head /> many times in a code base
  • Next makes sure that if children of any usage of <Head /> mutates, the actual <head /> tag gets updated

but there's a buggy implementation in place:

  • it will accumulate the count of children of the usages of <Head />
  • that number will be written to a meta tag with the name "next-head-count" <meta name="next-head-count" content="12"/>
  • A thing called the <HeadManager /> will iterate over the n previousElements of that meta tags while n is next-head-count
  • for every 'meta', 'base', 'link', 'style', 'script' it finds, it will just remove that, and if changed it will insert a new one with the new props

That does not play nice with

  • React components returning more than one element (wrong count)
  • your own first party code injecting stuff in the head (painful, you can do something about this)
  • third party scripts injecting stuff in the head (out of your control)
  • event listeners on dom elements (becuse the dom gets wiped on every rerender and, for instance, a load event on a script won't get emitted on a removed dom event even though the script has loaded

@mp205
Copy link

mp205 commented Feb 23, 2023

For anyone looking for a quick workaround, we've been using the following for the last half a year or so, and so far had no visible issues.

All pages return their unique ID in getServerSideProps (or alternatives), which is then used in all <meta> tags as a data value. Our _app.tsx uses the following hook to identify old elements that are not supposed to be there anymore and remove them.

const metaId = useMemo(
    () => `${pageProps.pageId}-${pageProps.locale}`,
    [pageProps.pageId, pageProps.locale],
  );

  useEffect(() => {
    document
      .querySelectorAll<HTMLMetaElement>('[data-meta-id]')
      .forEach((e) => {
        e.dataset['metaId'] !== metaId && e.remove();
      });
  }, [metaId]);

@alexdanilowicz
Copy link

I can repro in 13.0.7 was trying to set up localizejs, but seems Next strips the <script> out of the <Head>.

sokra pushed a commit that referenced this issue Apr 5, 2023
This adds an `experimental.strictNextHead` flag to allow updating head
tags tracking for pages to resolve the issue with runtime scripts being
appended to the head element breaking the head tracking.

Fixes: #11012
Fixes: #20682
x-ref: [slack
thread](https://vercel.slack.com/archives/C051B8JAPQ9/p1680156608831939)
@github-actions
Copy link
Contributor

github-actions bot commented May 5, 2023

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: Metadata Metadata (metadata, generateMetadata, next/head, head.js) good first issue Easy to fix issues, good for newcomers kind: bug Confirmed bug that is on the backlog type: needs investigation
Projects
None yet