gatsby-plugin-react-helmet orders the components too late in <head>

Created on 12 Mar 2020  ·  48Comments  ·  Source: gatsbyjs/gatsby

Description

When I use gatsby-plugin-react-helmet I notice that the <Helmet /> components come way too late in the head tag. I have noticed that on occasion for instance Facebook does not always parse the og tags if they come too late in the head.

Steps to reproduce

Just use <Helmet />, and notice in the static output files there is a lot of CSS related stuff etc. above the helmet tag in the <head>.

Expected result

Helmet tags should be prioritised when doing the <head /> tag.

Workaround

Currently there is easy workaround, but I consider that gatsby's default is broken. We should not need workaround for this.

gatsby-ssr.js

var React = require("react");

// Hack, to reorder the helmet components as first in <head> tag
exports.onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
    /**
     * @type {any[]} headComponents
     */
    const headComponents = getHeadComponents();

    headComponents.sort((a, b) => {
        if (a.props && a.props["data-react-helmet"]) {
            return 0;
        }
        return 1;
    });
    replaceHeadComponents(headComponents);
};
help wanted not stale feature or enhancement

Most helpful comment

I ran into the same issue. I tried checking the meta/og tags via several tools (e.g. Open Graph Check, Meta Tags, and Social Share Preview) and none of them were able to pick them up.

Once I mad a tweak similar to @Ciantic's I was able to retrieve them successfully.

I beleive this was mainly caused by a rather large style tag being placed before the meta tags.

My solution requried a bit more to get it working. I had to implement the following in my gatsby-ssr.js (you may be able to get away with just using the onRenderbody api, but I used the onPreRenderHTML api as well to move them completely to the top):

const React = require("react")
const { Helmet } = require("react-helmet")

exports.onRenderBody = (
  { setHeadComponents, setHtmlAttributes, setBodyAttributes },
  pluginOptions
) => {
  const helmet = Helmet.renderStatic()
  setHtmlAttributes(helmet.htmlAttributes.toComponent())
  setBodyAttributes(helmet.bodyAttributes.toComponent())
  setHeadComponents([
    helmet.title.toComponent(),
    helmet.link.toComponent(),
    helmet.meta.toComponent(),
    helmet.noscript.toComponent(),
    helmet.script.toComponent(),
    helmet.style.toComponent(),
  ])
}

exports.onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
  const headComponents = getHeadComponents()

  headComponents.sort((x, y) => {
    if (x.props && x.props["data-react-helmet"]) {
      return -1
    } else if (y.props && y.props["data-react-helmet"]) {
      return 1
    }
    return 0
  })

  replaceHeadComponents(headComponents)
}

All 48 comments

Do you have any "sources" about facebook skipping trying to extract og tags if they are not early enough?

I'm not sure but ordering of this stuff shouldn't matter as long as it's there.

In https://html.spec.whatwg.org/multipage/semantics.html#the-meta-element I don't see any notion about ordering. Only ordering restrictions relate to charset meta - https://html.spec.whatwg.org/multipage/semantics.html#charset :

The element containing the character encoding declaration must be serialized completely within the first 1024 bytes of the document.

So if facebook don't pick up those tags if they are later on in - it seems like it's facebook that doesn't follow spec?

No I don't have sources, this is a few years ago, I had the problem in entirely different stack. I watched the FB bot crawling and downloading only n-amount of bytes from the top, and if the og tags was not there it didn't parse them.

I also have Content-Security-Policy, and it's odd if it comes after the style files.

I gave the workaround in my main post already. If this is not actionable at the moment I will close this.

I ran into the same issue. I tried checking the meta/og tags via several tools (e.g. Open Graph Check, Meta Tags, and Social Share Preview) and none of them were able to pick them up.

Once I mad a tweak similar to @Ciantic's I was able to retrieve them successfully.

I beleive this was mainly caused by a rather large style tag being placed before the meta tags.

My solution requried a bit more to get it working. I had to implement the following in my gatsby-ssr.js (you may be able to get away with just using the onRenderbody api, but I used the onPreRenderHTML api as well to move them completely to the top):

const React = require("react")
const { Helmet } = require("react-helmet")

exports.onRenderBody = (
  { setHeadComponents, setHtmlAttributes, setBodyAttributes },
  pluginOptions
) => {
  const helmet = Helmet.renderStatic()
  setHtmlAttributes(helmet.htmlAttributes.toComponent())
  setBodyAttributes(helmet.bodyAttributes.toComponent())
  setHeadComponents([
    helmet.title.toComponent(),
    helmet.link.toComponent(),
    helmet.meta.toComponent(),
    helmet.noscript.toComponent(),
    helmet.script.toComponent(),
    helmet.style.toComponent(),
  ])
}

exports.onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
  const headComponents = getHeadComponents()

  headComponents.sort((x, y) => {
    if (x.props && x.props["data-react-helmet"]) {
      return -1
    } else if (y.props && y.props["data-react-helmet"]) {
      return 1
    }
    return 0
  })

  replaceHeadComponents(headComponents)
}

Thank you @ttstauss your fix worked perfectly.
This is an issue with whatsapp link sharing as well and other platforms that render previews. Hopefully it will be addressed in the main Gatsby release.

If this issue exists, then I will reopen this.

I think that Gatsby should handle the good ordering by default, and these hacks are just adding noise to us all trying to maintain configurations which should be in Gatsby itself.

What's the directory of gatsby-ssr.js?

What's the directory of gatsby-ssr.js?

@jun-gh, gatsby-ssr.js goes in the root of your project (https://www.gatsbyjs.org/docs/ssr-apis/)

What's the directory of gatsby-ssr.js?

@jun-gh, gatsby-ssr.js goes in the root of your project (https://www.gatsbyjs.org/docs/ssr-apis/)

I tried adding this on my root directory, but still Helmet tags are still under several style tags.

Hiya!

This issue has gone quiet. Spooky quiet. 👻

We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here.
If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request. Check out gatsby.dev/contribute for more information about opening PRs, triaging issues, and contributing!

Thanks for being a part of the Gatsby community! 💪💜

I don't have means to add labels. I wish one could just give reaction to that bot to not close it. Short of that, I have to comment.

We are happy to review a PR changing the behavior of gatsby-plugin-react-helmet however we won't work on this and thus the bot asking for activity is correct. We don't consider this being broken but a feature request.

I closed this, since that's what the bot would have done, if this is not actionable.

I would have thought that if the default gives a broken behavior as commented by others, it would be a bug. But others bug is someone's feature as the saying goes.

Edit: How can you read mr. @ttstauss and @dres90 comments as anything else than a bug? The default behavior leads to OG meta tags not being identified correctly, isn't that a bug?

For the record I also had to implement this fix and I think ordering of head elements should be a feature. I want that level of control right out of the plugin

Thank you @ttstauss . Your fix worked for me.

  • one that this should be a feature. @ttstauss 's fix worked for me.

@jun-gh I am also facing the same problem meta tags are still below the style tags, did you got a workaroud ?

Also facing this issue

No luck. I'm trying the prerender.io . I'll update if it works.

Martin I. Abanes
[email protected]

On Wed, Jul 8, 2020 at 8:30 AM Evan Rose notifications@github.com wrote:

Also facing this issue


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/gatsbyjs/gatsby/issues/22206#issuecomment-655209832,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AHI2Y5ZTZAEQXR4D5II7OTDR2O43DANCNFSM4LGNKWOQ
.

Hiya!

This issue has gone quiet. Spooky quiet. 👻

We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here.
If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request. Check out gatsby.dev/contribute for more information about opening PRs, triaging issues, and contributing!

Thanks for being a part of the Gatsby community! 💪💜

This should not be closed automatically....and I think this is not a feature request...header rendering control is part of template building in most of the web frameworks and it should be reviewed as it is a requirement for seo and social.

I have also tested all the proposed solutions here and none of it works with the latest gatsby.

I have also tested all the proposed solutions here and none of it works with the latest gatsby.

I am running

"gatsby": "^2.21.28",
"gatsby-plugin-react-helmet": "^3.3.10",
"react-helmet": "^6.1.0",

And none of the fixes above work. They used to work, but something recently has broken the hack described above.

I am also experiencing this issue, but I cannot rely on SSR solutions, so sadly I cannot try the suggested work-arounds.

I tried everything, from purgecss to moving og:meta as top as I can then lowering resolution to 600x600 and file size to 210Kb

still no thumb, please help us!

I was ignorant about the fact that gatsby-ssr.js can be used to manipulate static builds. In fact, I've tried @ttstauss 's solution and it appears to work fine!

Actually, it was working with @ttstauss 's solution. It didn't shows up for WhatsApp cache issues I guess.

Dropping in to share an example production case (+ sources) where this is happening: https://search.google.com/test/rich-results?id=7VKg0YQtEoYGPlBlTD60RQ

Google reports 'Page not eligible for rich results known by this test' but viewing the rendered HTML shows that the tags are indeed present.

My personal website has a tonne of styles injected by the Gatsby MUI plugin. Until recently it was OK, but recently I added an inlined SVG and after that, it looks like parsers have just given up looking for the meta tags that follow it.

Obviously I can work around this as others have, but FWIW it does seem to be an easy problem to run into. My particular case is a little more complicated than the above since I'm injecting a lot of styles/metadata on the client-side at the moment.

Im having this issue and @ttstauss 's work around doesn't seem to work for <meta> tags. But it does pull the <title> tag to the top!

I was trying @ttstauss 's solution, but for some reason, gatsby-browser.js and gatsby-ssr.js ignore onRenderBody and onPreRenderHTML. I also use wrapRootElement in the same file and it works fine.

import React from 'react'
import Helmet from 'react-helmet'
import Wrapper from './src/templates/pages-layout'

export const wrapRootElement = ({ element }) => <Wrapper>{element}</Wrapper> // works

export const onRenderBody = (
  { setHeadComponents, setHtmlAttributes, setBodyAttributes },
  pluginOptions
) => {
  console.log('onRenderBody')  // doesn't work
  ...
}

export const onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
  console.log('onPreRenderHTML') // doesn't work
  ...
}

Any ideas why?

Hiya!

This issue has gone quiet. Spooky quiet. 👻

We get a lot of issues, so we currently close issues after 60 days of inactivity. It’s been at least 20 days since the last update here.
If we missed this issue or if you want to keep it open, please reply here.
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request. Check out gatsby.dev/contribute for more information about opening PRs, triaging issues, and contributing!

Thanks for being a part of the Gatsby community! 💪💜

Open graph tags not being pulled in from a Gatsby site to Facebook/etc is a bug.

There is a bug here and it needs investigating. I will try and pull together a decent test case over the next week.

At one time the solution at the top worked fine, but as versions have progressed, the re-ordering has stopped completely.

So for example in my case

exports.onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
  const headComponents = getHeadComponents()

  headComponents.sort((x, y) => {
   console.log(x.props["data-react-helmet"]) // <---- THIS IS ALWAYS AN EMPTY ARRAY
  })

  replaceHeadComponents(headComponents)
}

So the ordering just doesnt work. I don't know if another plugin like gatsby-plugin-manifest is interfering somehow, but I tried a basic install and the above was always just empty. I know this isnt a proper bug report, but it might help someone.

Hi! I'm having the same issue as you guys. I have a huge style tag rendered first on the HTML when my intention is to put meta tags and hreflang on top of the head, since SEO best practices (and it's even noted on Google documentation) follows this guideline. I am working with Gatsby and still did not get something to work this way.

I will keep you updated about this fix, which is extremely delicate and basic for it to not have been handled by the plugin this far. Definitely NOT a feature.

Same problem here, the proposed workarounds did not work in my case. In my opinion this is a bug.

Something definitely changed. @nhc touched on it above. When looking at the headComponents (using getHeadComponents()), I see significantly fewer components than there were before, including a bunch of empty arrays. The only "data-react-helmet" component I see is the title which happens to be inside an array (so now the sorting would need to be done after the array is flattened I believe).

My initial testing shows only a select number of components are generated from getHeadComponents() at the time it's called, many of the other tags (including the large style tag, helmet tags, and other tags) are being placed before the small number of components we have access to sort. In some cases, I can't event get the small number of components we have access to sorted anymore.

Gatsby docs indicates the getHeadComponents returns the _current_ headComponents array. Gatsby docs also states the following for replaceHeadeComponents: WARNING if multiple plugins implement this API it’s the last plugin that “wins”. Not sure if the issue is related to when getHeadComponents is called or if there's another plugin accessing the API before/after.

Either way, I can confirm the previous hack does not work as it did before. I'm currently investigating the SEO implications this is potentially causing, but I do know various previews continue to be affected.

So far I've tested the following on two separate websites with success (I'm early in my testing so I can't guarantee that'll work for everyone):

export const onRenderBody = ({
  setHeadComponents,
  setHtmlAttributes,
  setBodyAttributes,
}) => {
  const helmet = Helmet.renderStatic()
  setHtmlAttributes(helmet.htmlAttributes.toComponent())
  setBodyAttributes(helmet.bodyAttributes.toComponent())
  setHeadComponents([
    helmet.title.toComponent(),
    helmet.base.toComponent(),
    helmet.meta.toComponent(),
    helmet.link.toComponent(),
    helmet.noscript.toComponent(),
    helmet.script.toComponent(),
    helmet.style.toComponent(),
  ])
}

export const onPreRenderHTML = ({
  getHeadComponents,
  replaceHeadComponents,
}) => {
  const headComponents = getHeadComponents()
  const order = ["title", "base", "meta", "link", "noscript", "script", "style"]

  const sortedHeadComponents = headComponents
    .slice(0)
    .flat()
    .sort((x, y) => {
      return order.indexOf(x.type) - order.indexOf(y.type)
    })

  replaceHeadComponents(sortedHeadComponents)
}

Notes on what I did differently:

  • I decided to avoid modifying the array in place by using the splice method.
  • I had to flatten the headComponents array in order to evaluate each item's type property (some of the components come in as array's instead of just objects).
  • I created an order array to dictate what tags I want where and applied it in the sort method.
  • I'm now sorting by _type_ as I felt this is the ultimate goal.

As I said, this has worked so far, but I'm still testing.

While developing (or running builds) I still don't know why I can't see all tags when calling getHeadComponents() (hence the empty arrays from my previous comment). I do see other tags that get missed with this approach, but for the most part, it seems to be working (I tested some link previews and they're working much better now).

If anyone thinks there's a more efficient approach I'd be happy to see it.

Edit: Also, gatsby-plugin-react-helmet's onRenderBody implementation is almost identical so I think removing that portion and relying on the plugin for the implementation details would be worthwhile (the plugin is well maintained and keeps up with react-helmet updates).

I'm not sure if sorting the tags would be considered a bug, since the purpose of this plugin is to just get it server-side rendered. Gatsby docs clearly state that onPreRenderHTML is a great option for rearrangement of tags (giving options specific to the head). On the flip side, one of the primary purposes is better SEO which would need better placement of certain tags I think. Does react-helmet handle _all_ head tags generated for a page, including ones injected by other plugins? Does this kind of option belong in this plugin specifically or should it be its own plugin?

Note: I ran this code on fresh builds (cleared cache) and in some cases needed to unregister any serviceWorkers.

Hi @tterb thanks for spending time on this. I tried your solution and although I see much less tags in development than in production, even in production I don't get the meta tags from React Helmet.

So the tags are being sorted, but the meta tags are last since I don't get them with getHeadComponents().

Hi, modifying gatsby-ssr worked for me, but just for the pages created with content from Contentful, not for the pages that are in src/pages directory. Anyone does know why? Or how to apply that solution to src/pages directory files? Thanks

+1 for this issue

Also having this issue—@tterb your solution worked for me!

Note for future travelers: I just learned that onPreRenderHTML doesn't execute in develop mode. That tripped me up at first, but doing a full gatsby build worked a treat. So if anyone is trying one of the solutions above and only verifying using gatsby develop, go ahead and try a full build before writing off the fixes above.

If I use @ttstauss solution, some meta tags go up, and they look like this:
<meta data-react-helmet="true" property="og:image:width" content="1200">

while some of them are still at the very bottom<head> and they look like this (data-react-head is at the end):
<meta property="og:image" content="/static/f205a9048d..." data-react-helmet="true">

In the case of the first solution from @Ciantic I get the same result.

I also came across a slightly different solution: link
Here, two meta tags have a data-react-helmet at the end (in the above solutions there was one), and they also go to the end of <head>.

Maybe a hint on how to fix it (So I am trying to move the meta tags to the top (or at least before the styles))?

Updating the comment:

I checked the public folder after the build, as well as the Insomia response - it turned out that the meta tags were sorted correctly. In the dev tools tab, a different order was shown to me and that confused me.

However, this was not the cause of the problem, because facebook received the correct tag sequence. The reason was an incorrect og: url. It was set to the home page, so the facebook robot was headed there where there were no proper meta tags. I hope this information will be useful to someone.

@ttstauss I tried your approach and it's working great. I'm not sure if it's a big problem for someone or not, but I see the HEAD elements get re-arranged when JS kicks in, which probably causes some tags to get unmounted. I've done some rearrangements to prioritize resources and now some preload hints I've had for LCP images seem to have taken a hit on key metrics.

The above solution mentioned by @ttstauss is still not working for me, cross checked my site with Meta tags.

Here is my seo.js

const SEO = ({
  description,
  lang,
  image: metaImage,
  meta,
  title,
  pathname,
  blogURL,
}) => {
  const { site } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
            description
            siteUrl
            social {
              twitter
            }
          }
        }
      }
    `
  )

  const image =
    metaImage && metaImage.src
      ? `${site.siteMetadata.siteUrl}${metaImage.src}`
      : null
  const metaDescription = description || site.siteMetadata.description
  const defaultTitle = site.siteMetadata && site.siteMetadata.title
  const pageURL = blogURL
    ? `${site.siteMetadata.siteUrl}/${blogURL}`
    : site.siteMetadata.siteUrl
  const canonical = pathname
    ? `${site.siteMetadata.siteUrl}${pathname}`
    : site.siteMetadata.siteUrl

  return (
    <Helmet
      defer={false}
      htmlAttributes={{
        lang,
      }}
      link={
        canonical
          ? [
              {
                rel: "canonical",
                href: canonical,
              },
            ]
          : []
      }
      titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : null}
      meta={meta}
    >
      {/* General tags */}
      <title>{title}</title>
      <meta name="image" content={image} />
      <meta name="description" content={metaDescription} />

      {/* Open graph tags */}
      <meta property="og:title" content={title} />
      <meta property="og:type" content="article" />
      <meta property="og:url" content={pageURL} />
      {metaImage && <meta property="og:image" content={image} />}

      <meta property="og:description" content={metaDescription} />

      {/* Twitter card tags */}
      <meta name="twitter:card" content="summary_large_image" />
      {title && <meta name="twitter:title" content={title} />}
      {metaImage && <meta name="twitter:image" content={image} />}
      {metaDescription && (
        <meta name="twitter:description" content={metaDescription} />
      )}
    </Helmet>
  )
}

Front matter:

title: How to write a resume!
date: 2015-05-01
description: 5 tips that would put your resume on top of applicant
featured: salty_egg.jpg
blogURL: how-to-write-a-resume

image

Not sure what I have done wrong!

@vamshi9 hmm, I'd double-check to make sure getHeadComponents.slice().flat() it's flattened if you have multiple <Helmet /> elements and if you'd written a plugin for this to make sure it's referenced in config at the end (replaceHeadComponents is racy).

Yeah, flattened array is coming fine, I had 2 issues in here. First issue was meta tags loading after the style tag, but this is resolved now. The second issue is out of the scope of this problem, I will figure it out. But thanks for the solution 👍

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rossPatton picture rossPatton  ·  3Comments

KyleAMathews picture KyleAMathews  ·  3Comments

mikestopcontinues picture mikestopcontinues  ·  3Comments

kalinchernev picture kalinchernev  ·  3Comments

dustinhorton picture dustinhorton  ·  3Comments