BU0
BU0

Reputation: 762

How can I inject arbitrary string HTML content into the head of my gatsbyjs site?

I have a GatsbyJS site that I am working on where the main content source is a Wordpress install. One of the things I like to add to my sites is the ability to have placeholder areas in the site where I can control the content via the CMS. Usually I have a header_scripts area that goes at the bottom of the <head> tag, a body_scripts area that goes at the start of the <body> tag, and a footer_scripts area that goes at the bottom of the page <body>. With these three, I can usually integrate third-party add-ins pretty easily without having to do code deployments.

Sometimes I need to embed stylesheets, sometimes I need to embed script tags, and sometimes I need to throw in <meta> tags. Really the content could be anything. This data comes back as a raw string from my Wordpress GraphQL endpoint.

So now my question is, how do I get this content injected into my Gatsby site in the following places:

<html>
  <head>
     ...
     {header_scripts}
  </head>
  <body>
    {body_scripts}
    ...
    {footer_scripts}
  </body>
</html>

I've found so far that I can just include the body_scripts and footer_scripts in a fairly regular manner in my Gatsby page template. In gatsby-node.js, I pass in the property values using the pageContext. It's kind of a bummer that they need to be wrapped in a <div /> tag, but they seem to work just fine.

import React from 'react'

export default class PageTemplate extends React.Component {
  render = () => {
    return (
      <React.Fragment>
        {this.props.pageContext.bodyScripts && (
          <div dangerouslySetInnerHTML={{__html:this.props.pageContext.bodyScripts}} />
        )}
        
        {/* my page content here */}

        {this.props.pageContext.footerScripts && (
          <div dangerouslySetInnerHTML={{__html:this.props.pageContext.footerScripts}} />
        )}
      </React.Fragment>
    )
  }
}

Now for the real question. I am stumped on how to get the dynamic content from the header_scripts into the Gatsby server-side-rendering <head> tag. The closest thing I have found to being able to inject content into the head is to leverage the gatsby-ssr.js onRenderBody function. However, this seems to require pre-determined React component instances in order to function. I can't just pass it in plain raw string content and see the output in the page source:

export const onRenderBody = async ({
  pathname,
  setHeadComponents,
  setHtmlAttributes,
  setBodyAttributes,
  setPreBodyComponents,
  setPostBodyComponents,
  setBodyProps
}, pluginOptions) => {
  setHeadComponents(['<script>alert("hello");</script>'])
}

This results in an escaped string getting inserted into the <head> tag:

<html>
  <head>
    ...
    &lt;script&gt;alert(&quot;hello&quot;);&lt;/script&gt;
  </head>
  <body>
    ...
  </body>
</html>

I'm at a loss as to how to proceed. I can't just wrap my string in a <div /> tag like in the body because div tags can't go inside the head tag. I can't think of any head-capable HTML tags that would accept this kind of content.

The only idea I've had is to actually parse the string content into full React components. This seems daunting given the number of possible tags & formatting that I would need to support.

Am I going about this the wrong way? How can I get my arbitrary content into my Gatsby site's head tag?

Upvotes: 2

Views: 1071

Answers (1)

Ferran Buireu
Ferran Buireu

Reputation: 29320

It's a broad question and it will need some trials and errors to ensure that it's fully working without caveats in all scenarios but, among the things you've tried, you can add a few more options to the list to check which ones fit better.

Regarding the body_scripts and footer_scripts both can be inserted using the:

<div dangerouslySetInnerHTML={{__html:this.props.pageContext.footerScripts}} />         

In any desired page or template. For the header_scripts and the meta tags (SEO), you can use the <Helmet> component. Basically, using this component, everything that is wrapped inside, it's becomes transpiled inside the <head> tag once compiled.

export default class PageTemplate extends React.Component {
  render = () => {
    return (
      <React.Fragment>
        <Helmet>
          {this.props.pageContext.headerScripts && (
           <div dangerouslySetInnerHTML={{__html:this.props.pageContext.headScripts}} />
          )}
        </Helmet>
        {this.props.pageContext.bodyScripts && (
          <div dangerouslySetInnerHTML={{__html:this.props.pageContext.bodyScripts}} />
        )}
        
        {/* my page content here */}

        {this.props.pageContext.footerScripts && (
          <div dangerouslySetInnerHTML={{__html:this.props.pageContext.footerScripts}} />
        )}
      </React.Fragment>
    )
  }
}

However, if the data comes from a CMS, it won't be available in the SSR yet, so, one easy thing you can do is to customize the outputted HTML (html.js) that Gatsby generates in each compilation. From the docs:

Customizing html.js is a workaround solution for when the use of the appropriate APIs is not available in gatsby-ssr.js. Consider using onRenderBody or onPreRenderHTML instead of the method above. As a further consideration, customizing html.js is not supported within a Gatsby Theme. Use the API methods mentioned instead.

Run:

cp .cache/default-html.js src/html.js

Or manually, copy the .cache/default-html.js file and paste it /src folder. There you can customize the final HTML.

Upvotes: 2

Related Questions