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

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

Closed
Ciantic opened this issue Mar 12, 2020 · 107 comments · Fixed by #34030
Closed

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

Ciantic opened this issue Mar 12, 2020 · 107 comments · Fixed by #34030

Comments

@Ciantic
Copy link

@Ciantic Ciantic commented Mar 12, 2020

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);
};
@pieh
Copy link
Contributor

@pieh pieh commented Mar 12, 2020

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.

@pieh
Copy link
Contributor

@pieh pieh commented Mar 12, 2020

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.

@pieh
Copy link
Contributor

@pieh pieh commented Mar 12, 2020

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?

@Ciantic
Copy link
Author

@Ciantic Ciantic commented Mar 12, 2020

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.

@Ciantic
Copy link
Author

@Ciantic Ciantic commented Mar 13, 2020

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

@ttstauss
Copy link

@ttstauss ttstauss commented Apr 24, 2020

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)
}

@Dres90
Copy link

@Dres90 Dres90 commented May 29, 2020

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.

@Ciantic Ciantic reopened this May 30, 2020
@Ciantic
Copy link
Author

@Ciantic Ciantic commented May 30, 2020

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.

@jun-gh
Copy link

@jun-gh jun-gh commented Jun 2, 2020

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

@ttstauss
Copy link

@ttstauss ttstauss commented Jun 2, 2020

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/)

@jun-gh
Copy link

@jun-gh jun-gh commented Jun 2, 2020

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.

@github-actions
Copy link

@github-actions github-actions bot commented Jun 23, 2020

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! 💪💜

@Ciantic
Copy link
Author

@Ciantic Ciantic commented Jun 23, 2020

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.

@LekoArts
Copy link
Contributor

@LekoArts LekoArts commented Jun 23, 2020

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.

@Ciantic Ciantic closed this Jun 23, 2020
@Ciantic
Copy link
Author

@Ciantic Ciantic commented Jun 23, 2020

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?

@nhc
Copy link
Contributor

@nhc nhc commented Jun 29, 2020

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

@EricEisaman
Copy link

@EricEisaman EricEisaman commented Jun 30, 2020

Thank you @ttstauss . Your fix worked for me.

@OWMC
Copy link

@OWMC OWMC commented Jul 3, 2020

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

@BagchiMB
Copy link

@BagchiMB BagchiMB commented Jul 7, 2020

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

@evanmrose
Copy link

@evanmrose evanmrose commented Jul 8, 2020

Also facing this issue

@jun-gh
Copy link

@jun-gh jun-gh commented Jul 8, 2020

@wardpeet
Copy link
Member

@wardpeet wardpeet commented Sep 27, 2021

are you all runnin gatsby-plugin-react-helmet? From the comments above it looks like the develop server is running. Does anyone have a reproduction?

@wardpeet
Copy link
Member

@wardpeet wardpeet commented Sep 27, 2021

I ran yarn create gatsby and enabled react-helmet. I added


      <Helmet>
        <meta property="og:title" content="The Rock" />
        <meta property="og:type" content="video.movie" />
        <meta
          property="og:url"
          content="https://www.imdb.com/title/tt0117500/"
        />
        <meta
          property="og:image"
          content="https://ia.media-imdb.com/images/rock.jpg"
        />
      </Helmet>

And I see the og:title bits.

<head>
	<meta charSet="utf-8" />
	<meta http-equiv="x-ua-compatible" content="ie=edge" />
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
	<meta name="generator" content="Gatsby 3.14.0" />
	<title data-react-helmet="true"></title>
	<meta data-react-helmet="true" property="og:title" content="The Rock" />
	<meta data-react-helmet="true" property="og:type" content="video.movie" />
	<meta data-react-helmet="true" property="og:url" content="https://www.imdb.com/title/tt0117500/" />
	<meta data-react-helmet="true" property="og:image" content="https://ia.media-imdb.com/images/rock.jpg" />
	<link as="script" rel="preload" href="/webpack-runtime-95840e645d44d8c85cb2.js" />
	<link as="script" rel="preload" href="/framework-f8079e2149a2624678fc.js" />
	<link as="script" rel="preload" href="/app-d31669480faf4cab4d14.js" />
	<link as="script" rel="preload" href="/component---src-pages-index-js-d31f053691350b7d2209.js" />
	<link as="fetch" rel="preload" href="/page-data/index/page-data.json" crossorigin="anonymous" />
	<link as="fetch" rel="preload" href="/page-data/app-data.json" crossorigin="anonymous" />
</head>

I know this is a very small site but this seems to work so if someone can share a more representative one so we can fix this issue, that would be great!

@Dan503
Copy link

@Dan503 Dan503 commented Sep 27, 2021

@wardpeet does it render that with JS disabled?

The issue I had was that (by default) Gatsby wasn't outputting that stuff in the production build when JS was disabled.

That means that it doesn't work for generating social media preview images since those sites don't run JS when fetching the images.

The solution in this comment worked for me:
#22206 (comment)
It has been a while since I last worked on a Gatsby project though.

@wardpeet
Copy link
Member

@wardpeet wardpeet commented Sep 27, 2021

It works when you install gatsby-plugin-react-helmet

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Sep 27, 2021

@wardpeet I think part of reproducing this is having very large/many tags in the head.

@sandren
Copy link

@sandren sandren commented Sep 27, 2021

@wardpeet I think part of reproducing this is having very large/many tags in the head.

Exactly. The bulky inline stylesheet buries the relevant meta tags so far down in the head that social media sites don't even process them.

@calamarico
Copy link

@calamarico calamarico commented Oct 6, 2021

In my case, as others had comment here, in onPreRenderHtml I haven't the meta tags, despite I use helmet, gatsby plugin is configured... So I solve the problem creating a new component that render only the metatags:

  <>
    <title>{siteMetaData.title}</title>
    {siteMetaData.description && <meta name="description" content={siteMetaData.description} />}
    {siteMetaData.url && <meta property="og:url" content={siteMetaData.url} />}
    {siteMetaData.title && <meta property="og:title" content={siteMetaData.title} />}
    ...
  </>
);

export default MetaTags;

And in gatsby-ssr I use it to set the head components in onRenderBody:

export const onRenderBody = ({ setHeadComponents }) => {
  setHeadComponents(<MetaTags key="seo" />);
};

@imtejagst
Copy link

@imtejagst imtejagst commented Oct 7, 2021

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);
};

@imtejagst
Copy link

@imtejagst imtejagst commented Oct 7, 2021

you have done a great job, keep it up

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Nov 17, 2021

@wardpeet Would adding this (from this answer):

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)
}

to the gatsby-ssr of the plugin be a good first step here? It seems to be the most common fix for people, even though it hasn't resolved for everyone.

@sandren
Copy link

@sandren sandren commented Nov 17, 2021

@wardpeet Would adding this (from this answer):

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)
}

to the gatsby-ssr of the plugin be a good first step here? It seems to be the most common fix for people, even though it hasn't resolved for everyone.

@graysonhicks The root of the problem is the inline critical CSS burying the meta tags beyond the parser's capability.

So we just need to make sure that the <meta> tags always come before <style> and <link> tags in the <head>. That would probably resolve the issue for everyone at once. 😀

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Nov 18, 2021

@sandren Ah, sorry! I forgot to paste the rest of that solution, which I think does that reordering. Edited the comment to add it.

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Nov 18, 2021

I do notice that solution puts link tags before meta. Any downsides to placing after as you suggest @sandren ?

@sandren
Copy link

@sandren sandren commented Nov 18, 2021

I do notice that solution puts link tags before meta. Any downsides to placing after as you suggest @sandren ?

@graysonhicks I don't believe there are any downsides since meta elements aren't render blocking, but applying automatic sorting of any kind should probably be considered a breaking change in behavior (version bump for gatsby-plugin-react-helmet) even if it does resolve this existing issue affecting many projects.

From what I remember, I think the HTML5 spec only has recommendations for the earliest elements, namely meta charset, meta viewport, and title, but in general I think this order would probably be best:

  1. <meta> charset
  2. <meta> viewport
  3. <title>
  4. <base>
  5. all other <meta> tags (closes this issue!)
  6. <style>
  7. <link>
  8. <script>
  9. <noscript>

Thank you for addressing this issue. I'm really happy to know that we'll have nice social media preview cards working on Gatsby sites again soon! 😀

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Nov 19, 2021

@sandren Started a draft PR here: #34030

It essentially does the grouping you mention above in onPreRenderHTML. One thing I noticed and am not sure of is that several meta tags, namely charset, viewport don't ever show up in the array of head components, so I believe they are inserted by default at the top in html.js. I've tested this locally and the logging looks correct, but for some reason gatsby-dev-cli is complaining about running npm run serve so I will install the fork in the repo and test that way.

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Nov 19, 2021

Also, not entirely sure how onPreRenderHTML and onRenderBody work together and if this sorting can just be done in one, or should be done the same in both.

@KyleAMathews Any thoughts?

@KyleAMathews
Copy link
Contributor

@KyleAMathews KyleAMathews commented Nov 19, 2021

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Nov 19, 2021

Okay, so should they both use the same ordering mechanism or does it matter?

@sandren
Copy link

@sandren sandren commented Nov 19, 2021

It essentially does the grouping you mention above in onPreRenderHTML. One thing I noticed and am not sure of is that several meta tags, namely charset, viewport don't ever show up in the array of head components, so I believe they are inserted by default at the top in html.js. I've tested this locally and the logging looks correct, but for some reason gatsby-dev-cli is complaining about running npm run serve so I will install the fork in the repo and test that way.

It looks like the tags present in html.js are already in the correct sequence (http-equiv is in the right spot for sites supporting IE). So as long as props.headComponents is sorted then the complete <head> should be in the correct sequence as well. 😀

@KyleAMathews
Copy link
Contributor

@KyleAMathews KyleAMathews commented Nov 19, 2021

Okay, so should they both use the same ordering mechanism or does it matter?

The body is literally just for the html body so there's no meta, etc tags there.

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Dec 3, 2021

Does anyone have a repo that I can use to reproduce this? Even if you've implemented one of the workarounds above, I am trying to test a fix in the framework itself. Let me know, thanks!

@m99coder
Copy link

@m99coder m99coder commented Dec 3, 2021

My issue is that https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-image interferes with this solution #22206 (comment) as it also uses setHeadComponents, I guess.

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Dec 3, 2021

@m99coder I believe the solution I am working on will always take place within the framework, after react-helmet.

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Dec 3, 2021

I think I was able to reproduce it here: https://github.com/graysonhicks/head-tags-test

Steps

  • git clone git@github.com:graysonhicks/head-tags-test.git
  • cd head-tags-test
  • nvm use (or use Node 14 however you do)
  • npm install
  • gatsby build
  • gatsby serve
  • http://localhost:9000
  • Open DevTools

Expected Result

All meta tags at the top of the head

Actual Result

Some meta tags stuck at the bottom/in the middle

I arbitrarily added as many head tags as I could 😅 (styles, links to page-data.json, gatsby-image, lots of og/twitter tags, etc.). See screenshot here to see tags added by Helmet coming up below the other meta tags with links in between. Going to test next with the change in framework to see if it resolves.

image

cc: @KyleAMathews

@m99coder
Copy link

@m99coder m99coder commented Dec 3, 2021

@graysonhicks That’s exactly what also happens in my case. If you wanna have a look at a “live” site: https://woodist.de. Can’t share the source, though.

@leandroboari
Copy link

@leandroboari leandroboari commented Dec 16, 2021

The problem is critical and no solution worked for my site.

@graysonhicks
Copy link
Collaborator

@graysonhicks graysonhicks commented Dec 16, 2021

@leandroboari I have an approved PR here #34030 . Just need to get it merged and released 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.