next.js
next.js copied to clipboard
duplicate meta tags in head
Verify canary release
- [X] I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System: Platform: darwin Arch: x64 Version: Darwin Kernel Version 21.4.0: Fri Mar 18 00:45:05 PDT 2022; root:xnu-8020.101.4~15/RELEASE_X86_64 Binaries: Node: 14.20.0 npm: 6.14.17 Yarn: 1.22.18 pnpm: N/A Relevant packages: next: 12.2.2 eslint-config-next: 12.0.10 react: 17.0.2 react-dom: 17.0.2
What browser are you using? (if relevant)
chrome, safari
How are you deploying your application? (if relevant)
No response
Describe the Bug
some meta tags in the head are duplicated.
this can be even seen on the official nextjs page (viewport meta tag):

interestingly, in chrome this happens on my computer only when in a guest / icognito window.
those duplication are not present on the SSR source, so its a hydration problem
Expected Behavior
no duplicate tags
Link to reproduction
https://nextjs.org/
To Reproduce
open https://nextjs.org/
use inspector, look at the head
Hi, it looks like this is related to a change in google tag manager where it's injecting a new script to the head breaking the next-head-count
order. Can you confirm this is similar to the case you are seeing in your project as well?
@ijjk
Can confirm. For some reason, GTM is inserting it's scripts above next-head-count
, causing meta tags duplication when navigating between pages
Hi, it looks like this is related to a change in google tag manager where it's injecting a new script to the head breaking the
next-head-count
order. Can you confirm this is similar to the case you are seeing in your project as well?
Hi, yes, that could be indeed the case, it uses GTM and it injects various stuff in the header, in our case i see a clarity.ms script and analytics:

Same issue here using Next.js v10.2.3. Any updates?
duplicate meta tags when use with preact. https://github.com/garmeeh/next-seo/issues/864
Any fix for this yet?
Same issue with Next.js v12
definitely not a silver bullet: but could we address by keeping a different count for scripts, meta tags, etc. rather than just one next-head-count we could keep one for each? Better yet would be some sort of hash of each specific tag so we know that this one already exists and don't need to create another m:d ().
Also seeing this issue in nextjs v12
Have you guys tried placing the GTM scripts at the top, immediately after the web page's opening <head>
tag?
👋🏻 Also encountering this and curious if there are any good work arounds.
👋🏻 Also encountering this and curious if there are any good work arounds.
Yes, switched back to wordpress !
Facing the same issue: The is duplicated on every page. The <meta name="viewport" .../> is duplicated after navigating, even if the viewport meta is not set.
Also having this problem - any plans to fix?
Hi everyone, please try moving any scripts that appends additional scripts, e.g., GTM, to _document
, and at the top of the <Head>
stack, so it doesn't mess up the next-head-count
order.
Any update on this? Facing the same issue after integrating google tag manager.
Any update on this? Facing the same issue
I'm actually not seeing this on the current NextJS site, so maybe someone from the team could chime in for how this was officially fixed and update the docs?
in our case, moving GTM to document resolved.
EDIT: it actually did not. Moving <Scripts />
to Document just meant they didn't load at all which removed the duplicates but obviously we need these scripts to load.
Edit 2: Okay, I have no idea what is going on here but the resulting solution
- Do not abstract these scripts to their own components, they need to use the
Script
component imported in_document
- Use strategy
beforeInteractive
on these scripts - Include these scripts as children of the
<Head />
component in_document
- Also include the affected meta tags that were previously in
_app
in_document
1 - they did not show up otherwise no matter what else changed (strategy, child of Head which yes broke NextJS rules but I tried it) 2 - they did not show up otherwise 3 - again, did not show up otherwise 4 - when we had a sitewide meta tag in _app it still got moved around until it was included in document
Our use case included the usual GTM inline script that appends to the document head as well as an external script.
Still had issues, ended up doing a custom implementation for GTM so that it at least didn't append itself to the head of the document - we do have other scripts bundled so that may still happen somewhere down the line and will require some further testing. For now:
import { gtmId } from "../../../constants"
import Script from "next/script"
import { useEffect } from "react"
export const GTMScript = () => {
useEffect(() => {
window.dataLayer = window.dataLayer ?? []
window.dataLayer.push({
"gtm.start": new Date().getTime(),
event: "gtm.js",
})
}, [])
return <Script src={`https://www.googletagmanager.comgtm.js?id=${gtmId}`} />
}
Thank you @pm0u . If possible please keep us updated if you find anything else.
Following up -
We ended up moving these scripts to afterInteractive
so they end up in the body rather than the head and this seems to work although I haven't stress tested bundling scripts with GTM
https://nextjs.org/docs/pages/api-reference/components/script#afterinteractive
This was primarily spurred after getting this linter error: https://nextjs.org/docs/messages/next-script-for-ga as well as the note in the docs there re: intended uses.
Following up -
We ended up moving these scripts to
afterInteractive
so they end up in the body rather than the head and this seems to work although I haven't stress tested bundling scripts with GTMhttps://nextjs.org/docs/pages/api-reference/components/script#afterinteractive
This was primarily spurred after getting this linter error: https://nextjs.org/docs/messages/next-script-for-ga as well as the note in the docs there re: intended uses.
So I am assuming the <Script />
tag is not in _document.js
. So did you move it to an _app.js
component or someplace else?
So I am assuming the
<Script />
tag is not in_document.js
. So did you move it to an_app.js
component or someplace else?
Correct, these scripts are now in _app.tsx
. Also just for the record we are using the pages router currently on Next 13.4
@pm0u so I shifted the <Script />
tag in the _app.tsx
file, but, duplication in my case still persists. Do you have any idea on why this may not have worked in my case OR any other possible solutions on your mind?
Any update on this? Facing the same issue. I think the problem it's the GTM script
I ended up slightly modifying the default gtm loading script to essentially not allow next-head-count
to mess things up because of time constraints. However, the issue still persists in next 12.
The default gtm script finds the first <script />
element and loads all gtm scripts before it. Now you can change the first step to be something else like find the first css link tag or anything else that you see fit, and then load the gtm scripts below it. Just make sure it is below the <meta name="next-head-count" />
tag.
Also another thing to note is that you could upgrade to next13 and try the experimental.nextStrictHead flag which throws next-head-count
out the window, but I was unable to try it due to constraints on the next version we use at out company.
@hitarthdesai thank you so much for the update. Actually i have the same problem: my company still use the 12 version of Next. Do you have any implementation example of how did you changed the script?
I did the following:
var p=d.head.getElementsByTagName("meta").namedItem("next-head-count")
p.parentNode.insertBefore(i, p.nextElementSibling);
p.parentNode.insertBefore(j, i.nextElementSibling);
where j
is the script
element created in the default gtm script. All I changed was how they created a var f = ...
and used the above replacement instead.