Next.js: Duplicate meta tags when using Head both in custom document and page

Created on 20 Dec 2019  ·  25Comments  ·  Source: vercel/next.js

Bug report

Edit +++ ON HOLD I just had a lightbulb moment. I will post my discovery/misunderstanding in the next few hours. This ticket can probably be closed then, but I have to test my new theory first…

Describe the bug

Multiple (duplicate) meta tags such as charSet and viewport are rendered in <head> section of the HTML output when the <Head> component is used in both a custom _document and a page file.

Even relying on the default injection of <meta charSet="utf-8"/> (i.e. not specifying that tag at all) results in having two of the same tags in the output HTML, one from Document and one from the page.

I also end up with two viewport meta tags, one from Document and one from the page.

To Reproduce

I have created (failing) tests here: https://github.com/Manc/next.js/tree/test-head-document

  1. Clone repository [email protected]:Manc/next.js.git
  2. Check out branch test-head-document
  3. yarn install
  4. yarn testonly --testPathPattern "app-document" -t "It dedupes head tags with the same key"

Expected behavior

I expect the meta tags with the same key to override the meta tags from the custom Document (_document).

I expect only to have one charset and one viewport tag in the whole rendered HTML.

System information

  • OS: macOS
  • Version of Next.js: latest or canary branch

Other

I'm pretty new to Next.js and don't really know how it all works yet. I hope the test I provided helps somebody more experienced to fix this or maybe give me a hint. Thanks!

My suspicion is that the Head component of the Document (import { Head } from 'next/document') and the Head component used in the page (import Head from 'next/head') might be completely separate and don't "communicate" with each other, each doing their own thing twice.

Most helpful comment

I have the same issue. I set my meta viewport in _document.js. Still a second meta viewport is added:

<meta name="viewport" content="width=device-width"/>

I do not set it myself.

All 25 comments

I had the same problem

OK, here we go… As suspected earlier I finally worked it out.

Apparently, the correct way of using the two different (!) Head components is to prepare the meta tags etc. only in the page component or other components, but not in the _document.js file. In the _document only include it from next/document like <Head /> without any children.

To clarify…

// pages/_document.tsx

import Document, { Head, Main, NextScript } from 'next/document'; // We import `Head` from `next/document`!

class MyDocument extends Document {
    public render() {
        return (
            <html lang="en">
                <Head /> // Do not add any children here!
                <body>
                    <Main />
                    <NextScript />
                </body>
            </html>
        );
    }
}

export default MyDocument;
// pages/examplepage.tsx

import React from 'react';
import Head from 'next/head'; // We import `Head` from `next/head `!
import Layout from '../components/layout';

class ExamplePage extends React.Component {
    render() {
        return (
            <Layout>
                <Head>
                    <title>Example page</title>
                    <meta name="description" content="Example page description" />
                </Head>

                <h1>Example page</h1>
            </Layout>
        );
    }
}

export default ExamplePage;

And then in the Layout component you can define default meta tags and headers, but not in the _document itself.

I was used to the way react-helmet works. There you can set defaults at top level and override/add values in children, but you always use the same component. Here we use two different components with the same name, which, I guess, caused the confusion.

@Manc why have you closed this issue? It's clearly a bug.

_document.js is the place to specify the defaults for the whole site like charset and viewport.

Now we've got them duplicated with "defaults" later. Even using key prop like in this test scenario
https://github.com/zeit/next.js/blob/18a9c7e371efc4c487f9c3599c3211ce30009d6c/test/integration/client-navigation/pages/head-duplicate-default-keys.js

doesn't help. In my case, I've got two viewport tags, first one is mine

<meta name="viewport" content="width=device-width, initial-scale=1"/>

defined in _document.js using next/head and then the default one later

<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"/>

The same with charSet.
Please, reopen this issue, maybe we'll get an explanation.

@gothy You might be right, it's probably best to get the opinion of the Next.js team, what the intended usage is.

If we are supposed to define defaults in the _document.js, which makes sense, then it looks like a bug indeed. If one is supposed to prepare all the head tags outside _document.js (as described in my previous comment), then it should be made clearer and maybe the Head component of next/head should not even accept children.

next/head should be used in _app / pages.

_document is only for the initial document HTML and if you're going to override on the page level it won't work as we simply won't know about it on the client-side as _document is server-side only.

@timneutkens thanks for clarifying!

I had the impression, _document.js should contain all of the basic\shared\non-dynamic things like charset, viewport, manifest, icons, etc.

If I move the <meta charSet="utf-8" /> to the _app.js, I'm getting

Error: A charset attribute on a meta element found after the first 1024 bytes.

from validator.w3.org . I'm not sure if that's super bad, but it's not clear how can I avoid this without putting this tag first inside the _document.js <Head> since there's a huge list of i18n strings is being embedded in _document.js. Anything listed in _app.js is serialized after initial portion during SSR.

I've seen this PR on deprecating next/head. It would be great if the new take on this static\dynamic head handling will take W3 validation into account.

And thanks for Next 🚀😀

As said _document is for the initial html, so head elements that never change are fine there. In general you don’t need to add a charset as next comes with UTF-8 by default.

Yes. The (W3 validator) issue arises only when you've got something big serialized into the head in _document.js, since charset meta tag always comes after that.

My experience was, if I remember correctly now, as soon as I start adding defaults in the _document head, I end up with duplicate meta tags.

@gothy I also wanted to follow the recommendation to set charSet as far at the top as possible. This does not seem to be guaranteed if you rely on Next's defaults.

My solution (workaround?) is, as described above, do not set any defaults in the _document head, only in pages etc. For example, I use a Layout component, used by all pages and I set my default meta tags there and override them in individual pages.

@Manc yeah that's correct, defaults in _document is fine, if you want to override however you need everything available client-side so that we can actually dedupe and recalculate the items.

I'll close the issue now that it's cleared up.

I think this is still a problem. We have to have some things in our document.js <Head> which will "push down" all other tags in the <head>. The result is that the charSet meta is too far down the document according to things like W3 validator. There is no solution for this currently. As we have seen, putting the meta on top of the mandatory scripts in document.js is not a solution.

Adding an example of what @rscharfer is referring to:

_document.js

<Html>
  <Head>
    <meta charSet="utf-8" /> {/* Trying to add this at the very top of <head> */}
    {/* scripts, RSS feed etc. */}
  </Head>
</Html>

This will lead to duplicate meta charset tags.

And without adding the tag explicitly, Next will add it after your custom tags, which in this example would be way too far down. Adding the tag in a page component doesn't work either; Next will append it.

Is there a way to prevent the extra addition of the charSet meta tag in the lib/head.js file, even explicitly (for instance, by using a flag)? In general, its usage is problematic: the tag is inserted way too low in the head, therefore causing site validation to throw errors. This seems to be problematic, even if head is added to the _app.js file.

I have the same issue. I set my meta viewport in _document.js. Still a second meta viewport is added:

<meta name="viewport" content="width=device-width"/>

I do not set it myself.

A solution I found for this was to leave the _document as is, but add the viewport metadata in a next/head component inside each page. This will cause the "page" viewport metadata to override the default one set by _document and leaves the charset metadata (set by _document) at the very top.

I ended up creating a custom Head component (that uses next/head) which already sets the viewport metadata (so I don't have to repeat it on every page).

I'm using the SSG of Next.js, so maybe this doesn't apply for SSR pages, don't know. Hope it helps 👍

Also seeing a duplicate meta viewport overriding the one I specify:

image

A solution I found for this was to leave the _document as is, but add the viewport metadata in a next/head component inside each page. This will cause the "page" viewport metadata to override the default one set by _document and leaves the charset metadata (set by _document) at the very top.

I ended up creating a custom Head component (that uses next/head) which already sets the viewport metadata (so I don't have to repeat it on every page).

I'm using the SSG of Next.js, so maybe this doesn't apply for SSR pages, don't know. Hope it helps +1

Do you also use redux? because, i'd same problem when i declare next/head in pages/index.js it not show meta, but when i placing it in withReduxStore (custom wrapper for redux store like with-redux-store.js in this link : https://github.com/vercel/next.js/issues/8240), it generate double meta tag on detail page.

maybe next/head should not have the default meta, like viewport and charset

as overwrite in every page is not a good idea

I keep seeing Next/Head with meta tags and style sheet refs in both _document and _app files in nextjs examples. Are there any advantages in setting it in _document?

I have the same issue. I set my meta viewport in _document.js. Still a second meta viewport is added:

<meta name="viewport" content="width=device-width"/>

I do not set it myself.

Also getting this. Did you find a fix @switz or @hk86?

Either this should be reopened, or the examples should be cleared up: i.e. the Google Analytics example has script tags in the <Head> component of the _document, but doing so puts the aforementioned scripts before the charset declaration, and as noted in this issue there is with no way to override this without duplication.

So either offer a way to override, or document explicitly that you should avoid putting scripts (or anything actually) in the of the _document if you want it to come after the charset declaration (which should be in the first 1024 bytes).

Of course I'd rather have the override, as it solves even the viewport meta tag problem.

I haven't found a fix. More a workaround. I set

import Head from 'next/head';
<Head>
  <meta key="viewport" name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover" />
</Head>   

at _app. If I set it at _document

import { Head } from 'next/document';
<Head>
  <meta key="viewport" name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover" />
</Head>   

I end up with a second viewport:

<meta key="viewport" name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover" />
<meta name="viewport" content="width=device-width"/>

Either this should be reopened, or the examples should be cleared up: i.e. the Google Analytics example has script tags in the <Head> component of the _document, but doing so puts the aforementioned scripts before the charset declaration, and as noted in this issue there is with no way to override this without duplication.

So either offer a way to override, or document explicitly that you should avoid putting scripts (or anything actually) in the of the _document if you want it to come after the charset declaration (which should be in the first 1024 bytes).

Of course I'd rather have the override, as it solves even the viewport meta tag problem.

You're right and I'm putting head scripts in my Layout component with next/head

I updated on this issue: https://github.com/vercel/next.js/discussions/17020

We have this issue with our TMS. And realized anytime we have a script that does a document.head.append, a meta-tag gets duplicated in the Head. If we just add a script to our Layout, which basically does a document.createElement and then document.head.append, a meta-tag gets duplicated. If I add multiple calls to this, then multiple meta-tags get duplicated. The interesting part, is I am not even adding meta-tags to the Head, I am adding div tags to the head and it just randomly duplicates a meta-tag.

Was this page helpful?
0 / 5 - 0 ratings