skip to Main Content

I’m developing my website with Next.js To upgrade my website’s SEO performance, I’m trying to avoid duplicated meta tags.

My Question

In Next official docs, they say that I can avoid overlapped meta tag by insert key property in meta tag. But this does not work.

<meta name="description" content="~~" key="titleDescription"/>
<meta name="keywords" content="~~" key="titleKeywords"/>

These are default rootDocument meta tags and,

<meta name="description" content={item.product_description} key="titleDescription"></meta>
<meta name="keywords" content={item.brand_name} key="titleKeywords"></meta>

These are dynamically generated meta tags in item pages.

In deployed browser, there are still two description and keywords meta tags in website. I want to avoid duplicated meta tag. Thank you for your help!

4

Answers


  1. UPDATE

    Here’s what Next.js docs say regarding the <Head /> element within the _document.js file:

    The component used here is not the same one from next/head.
    The component used here should only be used for any
    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.

    Source: https://nextjs.org/docs/advanced-features/custom-document

    Therefore, Next.js recommends that all the dynamic <meta /> tags should only be defined on an individual page level.


    ORIGINAL ANSWEAR (it seemed to work at first, but as it turns out, only at random).

    This issue seems to appear when you mix up two ways of closing empty HTML tags in JSX. Notice that your meta tags in the _document.js file are self-closing: <meta /> and the dynamic ones are not: <meta></meta>.

    Ensure you are consistent with it (i.e. according to best practices you should convert all empty tags to the self-closing variant) and you should be fine.

    Login or Signup to reply.
  2. I believe you can take a look at this package that helps managing meta tags https://github.com/garmeeh/next-seo in Next.js project. It helped me 😀

    For your question, yea try putting the Head in the _app instead of _document. _document is like non mutated one.. it will be always in the website. Cheers 🥂

    Login or Signup to reply.
  3. There are a few things to watch for when using Next.js Head components.

    • The _document Head component is next/document
    • Tags in _document will not be replaced (can be duplicated) even with a key identifier
    • The _app Head component is next/head
    • Tags in _app can be in child components
    • Tags in _app can be overridden with the key identifier
    • Tags in pages cannot be in child components
    • Tags in pages can override tags with the same key identifier.

    Examples


    _document.{js|jsx|ts|tsx}

    Scripts, styles, and other tags you need access to immediately – Next will likely not be fully mounted at this point. You cannot replace tags in the document/head component with the key attribute.

    import Document, {Head, Html, Main, NextScript } from 'next/document';
    class MyDocument extends Document {
      render = () => (
        <Html lang="en-US" dir="ltr">
          <Head>
            <script src="/some-script.js" defer={false} />
            <style>.inline-styles{}</style>
            {/* META & TITLE TAGS PLACED HERE CANNOT BE OVERRODE */}
          </Head>
          <Head />
          <body>
            <Main />
            <NextScript />
          </body>
        </Html>
      );
    }
    
    

    OR self-closing Head tag

    class MyDocument extends Document {
      render = () => (
        <Html>
          <Head />
          <body>
            <Main />
            <NextScript />
          </body>
        </Html>
      );
    }
    
    

    Note: import is from next/document


    _app.{js|jsx|ts|tsx}

    import Head from 'next/head';
    
    const App = ({ Component, pageProps }) => (
      <>
        <Head>
          <title key="title">App Tittle</title>
        </Head>
        <Component {...pageProps} />
      </>
    );
    
    

    OR load from a custom component

    import Head from 'next/head';
    
    const MyHeadTags = () => (
     <Head>
      <title key="title">App Tittle</title>
      <meta key="description" name="description">Description</meta>
     </Head>
    );
    
    const App = ({ Component, pageProps }) => (
      <>
        <MyHeadTags />
        <Component {...pageProps} />
      </>
    );
    

    Note: import is from next/head


    some_page.{js|jsx|ts|tsx}

    const SomePage = () => (
      <>
        <Head>
          <title key="title">Some Page Tittle</title>
          <meta key="description" name="description">Some Page Description</meta>
        </Head>
        <div>My Page</div>
      </>
    );
    

    NOT ALLOWED

    The head component must be on the page and cannot be a child component. If it’s a child component, it sometimes will not override other tags with the same key property.

    some_page.{js|jsx|ts|tsx}

    const MyHeadTags = () => (
     <Head>
      <title key="title">Some Page</title>
      <meta key="description" name="description">Some Page Description</meta>
     </Head>
    );
    
    const ChildComponent = () => (
     <>
      <MyHeadTags />
      Info.
     </>
    );
    
    const SomePage= () => (
      <>
        <ChildComponent />
        <div>Will not work</div>
      </>
    );
    

    Update: The last example seems to work in some cases with Next 11+, but I have run into a few instances where it duplicates tags. I avoid the last method.

    Login or Signup to reply.
  4. I use Next 12.
    Thanks to Sean’s comment here, I was able to progress.

    I personally update the tags for SEO purpose according to the consulted page with a component Seo that I crafted.
    But to prevent importing it in every pages, I wanted to import it once. It happened that importing this Seo component in pages/_app.js was eventually just duplicating the tags and especially breaking my social media links.

    So here is my code.

    First the Seo component:

    
    import Head from 'next/head'
    import {SEO} from '@/Constants/seo'
    
    const titlePrepend = 'WeekandGO | '
    const Seo = ({ seo }) => {
      const currentUrl = typeof window !== 'undefined' && window.location.href
    
      const defaultSeo = {...SEO['index']}
    
      let seoWithDefaults = {...defaultSeo}
    
      if (seo) {
        seoWithDefaults = {
          ...seoWithDefaults,
          ...seo,
        }
      }
    
      const fullSeo = {
        ...seoWithDefaults,
        metaTitle: seoWithDefaults?.metaTitle ? `${titlePrepend}${seoWithDefaults.metaTitle}` : `${titlePrepend}${defaultSeo.metaTitle}`,
        shareImage: seoWithDefaults?.shareImage || defaultSeo.shareImage,
        metaDescription: seoWithDefaults?.metaDescription || defaultSeo.metaDescription,
      }
    
      return <Head>
        <title>{fullSeo.metaTitle}</title>
    
        <meta name="Author" lang="fr" content="Leb Communication"/>
        <meta name="Identifier-URL" content="www.weekandgo.com"/>
        <meta name="Reply-to" content=""/>
        <meta name="revisit-after" content="7"/>
        <meta name="Publisher" content="WeekandGO"/>
        <meta name="Copyright" content="WeekandGO"/>
        <link rel="canonical" href="https://www.weekandgo.com/" />
        <meta name="keywords" content={fullSeo.metaKeywords} />
        <meta name="description" content={fullSeo.metaDescription} />
        <meta name="image" content={fullSeo.shareImage} />
    
        <meta property="og:site_name" content="WeekandGO" />
        <meta property="og:url" content={currentUrl || 'https://www.weekandgo.com/'} />
        <meta property="og:type" content="website" />
        <meta property="og:image" content={fullSeo.shareImage} />
        <meta property="og:title" content={fullSeo.metaTitle} />
        <meta property="og:description" content={fullSeo.metaDescription} />
    
        <meta name="twitter:title" content={fullSeo.metaTitle} />
        <meta name="twitter:description" content={fullSeo.metaDescription} />
        <meta name="twitter:image" content={fullSeo.shareImage} />
      </Head>
    }
    
    export default Seo
    
    
    

    I use it in pages/_document.js:

    
    class MyDocument extends Document {
      render() {
        return (
          <Html lang="fr">
            <Head>
              <meta name="viewport" content="width=device-width, initial-scale=1" />
              <link rel="icon" href="/favicon.ico" />
              <meta name="theme-color" content="#000000" />
              <link rel="apple-touch-icon" href="/logo192.png" />
              <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap&subset=latin-ext" rel="stylesheet" />
              <link rel="stylesheet" href="https://use.typekit.net/uah4cdm.css" />
              <link rel="manifest" href="/manifest.json" />
              <script src="https://maps.googleapis.com/maps/api/js?key=<key>" type="text/javascript"/>
            </Head>
            <Seo/>
            <body className={ !isMobile && 'u-hover-on'}>
              <Main />
              <NextScript />
            </body>
          </Html>
        )
      }
    }
    
    

    Then I import this component in pages/_document.js as so:

    
    import Document, { Html, Head, Main, NextScript } from 'next/document'
    import { isMobile } from 'react-device-detect'
    import Seo from '@/Shared/Seo'
    
    class MyDocument extends Document {
      render() {
        return (
          <Html lang="fr">
            <Head>
              <link rel="icon" href="/favicon.ico" />
              <meta name="theme-color" content="#000000" />
              <link rel="apple-touch-icon" href="/logo192.png" />
              <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap&subset=latin-ext" rel="stylesheet" />
              <link rel="stylesheet" href="https://use.typekit.net/uah4cdm.css" />
              <link rel="manifest" href="/manifest.json" />
              <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDiB40c-5x-GAEBw3mIJXPTvamFHUV536I" type="text/javascript"/>
            </Head>
            <Seo/>
            <body className={ !isMobile && 'u-hover-on'}>
              <Main />
              <NextScript />
            </body>
          </Html>
        )
      }
    }
    
    export default MyDocument
    
    

    Then, let me spare you some lines to just look at the return of my functional component App in pages/_app.js. There is no import of Seo component, only a ** Head coming from ‘next/head’ ** is used :

    ...
      return (<SessionProvider session={session}>
        <TokensRefresher/>
        <Head>
          <meta charSet="UTF-8"/>
          <meta name="viewport" content="width=device-width, initial-scale=1" />
        </Head>
        <GlobalContextProvider appProps={{ global, categoryAnnexes, categoriesInfos }}>
          <GlobalLayout error={error}>
            <Component {...rest} />
          </GlobalLayout>
        </GlobalContextProvider>
      </SessionProvider>)
    }
    ...
    
    

    And finally, here is a page I receive data from server side props and that I want to use to change the meta tags related to seo. Here again I don’t show the part of the code that are useless for the matter :

    ...
    const Annonce = ({ ad, ...pageProps }) => {
      return <>
        <Seo seo={{metaTitle: ad?.slug, metaDescription: ad?.dealerWord, shareImage: ad?.firstImage?.fileUrl}} />
        <AnnonceLayout ad={ad} {...pageProps} />
      </>
    }
    ...
    

    The result I have in the source code is there is no duplicated meta tags and I can share my links on social medias with no image, description or page tilte overlaps

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search