RFC: Gatsby Head API #35841
-
SummaryWe will be adding a built-in metadata management solution to Gatsby. This will provide an opinionated way to approach metadata in Gatsby sites and provide full compatibility with React 18’s suspense/streaming capabilities. As of now, users install third-party packages like Basic example
import React from "react"
import { graphql } from "gatsby";
//Regular page function component
const MyPageTemplate = ({ pageContext, data, location, path, ...otherProps }) => {
return (
<div>
{/*Use props in template*/}
</div>
)
}
//Regular page query
export const pageQuery = graphql`
query ContentfulAboutPage($id: String!) {
contentfulPage(id: { eq: $id }) {
...ContentBlocks
...SocialMediaImage
name
title
subtitle {
subtitle
}
}
}`
// will receive same props as MyPageTemplate above
export const Head = ({ pageContext, data, location, path, ...otherProps }) => {
return (
<>
<title>{data.name}</title>
<meta name="description" content={data.subtitle.subtitle} />
<link rel="canonical" href="https://www.example.com/about" />
<link
rel="alternate"
hrefLang="en"
href="https://www.example.com/about"
/>
<link
rel="alternate"
hrefLang="es"
href="https://www.example.com/es/about"
/>
</>
)
}
export default MyPageTemplate; Try it outYou can try this feature by installing the latest canary version (experimental, not production-ready) on your site
OR
MotivationOur current recommendation for managing metadata on Gatsby sites, while powerful, is not as simple as it could be. This is not our first attempt at solving this problem, and to quote that original spec:
More recently, customers said this on the topic at GatsbyConf 2022:
Detailed designThink of this as a way to add a link, meta, and title tag to a page but outside of the JSX page template itself. We already use this pattern for two existing APIs:
We’re introducing a named export function named " The Usage for Globals & OverridesOne common pattern is to have global metadata that you can also override across pages. This is attainable by creating components. that render globals and use them everywhere //file: src/utils.js
import React from "react";
import { useStaticQuery, graphql } from "gatsby";
export function GlobalHead({ title , children, ...otherProps }){
const data = useStaticQuery(graphql`
query HeaderQuery {
site {
siteMetadata {
title
}
}
}
`);
return (
<>
<title>{title}<title>
<meta name="GLOBAL" description={data.site.siteMetadata.title} />;
{/*Some other globals*/}
{ children }
</>
)
} //file: src/pageA.js
import { GlobalHead } from "./utils"
function PageA(){
return(
<div>
Page A content
</div>
)
}
export function head(){
return (
<GlobalHead title="Page A">
{/*page specific head additions*/}
<GlobalHead>
)
}
export default PageA; //file: src/pageB.js
import { GlobalHead } from "./utils"
function PageB(){
return(
<div>
Page A content
</div>
)
}
export function head(){
return (
<GlobalHead title="Page A">
{/*page specific head additions*/}
<GlobalHead>
)
}
export default PageB; Drawbacks
AlternativesA prevalent alternative is the component approach which can be implemented in the following ways
Generally, the approach has some drawbacks that are closely tied to React 18 streaming/suspense. Non-deterministic loading order for resources, delayed discovery of resources and non-deterministic conflict resolution are issues users could run into with React as of today. Adoption strategyWe intend to make this the default solution for adding metadata to your Gatsby pages. For now, this is especially interesting for those experiencing suspense/streaming-related issues. There is no immediate need to migrate if you don’t run into such problems; however at a later stage, we recommend migrating once the composition drawback is resolved. How we teach thisWe’d need to make it clear to users that we now have a built-in solution for managing page metadata in Gatsby sites. Existing documentation like https://www.gatsbyjs.com/docs/add-seo-component/ and https://www.gatsbyjs.com/docs/how-to/adding-common-features/seo/#page-metadata Questions for YouWe’d love your feedback on the following :
RFC Changelog
|
Beta Was this translation helpful? Give feedback.
Replies: 34 comments 117 replies
-
One thing not mentioned here is the concept of meta overrides and templates which both In our case, we have an SEO component, which is a wrapper around // SEO.tsx
import React, { FC } from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import { Helmet } from 'react-helmet'
type SEOProps = {
description?: string
title?: string
metaImage?: string
keywords?: string[]
}
type Query = {
site: {
siteMetadata: {
title: string
defaultTitle: string
description: string
language: string
keywords: string[]
siteUrl: string
}
}
mainImage: { publicUrl: string }
}
export const SEO: FC<SEOProps> = ({
description,
keywords,
title: pageTitle,
metaImage: pageImage,
}) => {
const {
site: {
siteMetadata: {
title,
defaultTitle,
description: defaultDescription,
language: lang,
keywords: defaultKeywords,
siteUrl,
},
},
mainImage: { publicUrl: metaImageUrl },
} = useStaticQuery<Query>(graphql`
query {
site {
siteMetadata {
url
siteUrl
title
defaultTitle
description
language
keywords
}
}
mainImage: contentfulAsset(
contentful_id: { eq: "3zQPwOSOZIDpJGzLt2HjX9" }
) {
publicUrl
}
}
`)
const metaTitle = pageTitle || defaultTitle
const metaDescription = description || defaultDescription
const seoKeywords = { ...keywords, ...defaultKeywords }
const metaImage = pageImage || `${siteUrl}${metaImageUrl}`
return (
<Helmet
htmlAttributes={{
lang,
}}
title={metaTitle}
titleTemplate={`%s | ${title}`}
meta={[
{
name: `description`,
content: metaDescription,
},
{
property: `og:title`,
content: title,
},
{
property: `og:description`,
content: metaDescription,
},
{
property: `og:image`,
content: metaImage,
},
{
property: `og:type`,
content: `website`,
},
{
name: `twitter:card`,
content: `summary_large_image`,
},
{
name: `twitter:title`,
content: title,
},
{
name: `twitter:description`,
content: metaDescription,
},
{
name: `twitter:image`,
content: metaImage,
},
].concat(
seoKeywords.length > 0
? {
name: `keywords`,
content: seoKeywords.join(`, `),
}
: []
)}
/>
)
} // Layout.tsx
export const Layout: FC<Props> = ({ children }) => {
return (
<>
<SEO />
...
</>
)
} // templates/team.tsx
const TeamPage: FC<PageProps<TeamPageProps>> = ({ data }) => {
const { contentfulTeam } = data
return (
<Layout>
<SEO title={contentfulTeam.teamName} />
...
</Layout>
)
} One benefit that Gatsby could introduce as part of this, is a standardised way of creating meta images. So you could query contentfulAsset(contentful_id: { eq: "3zQPwOSOZIDpJGzLt2HjX9" }) {
gatsbyMetaImage
} |
Beta Was this translation helpful? Give feedback.
-
First – thank you for making this a part of the framework ✨🙏 I have some questions and thoughts. Is there a good reason why separate If there are no good "technical" reasons – why separate them – I would love to get it simpler with something like this:
With that we:
And I still think, having the option to use something like this // src/components/SEO.tsx
import { Head } from 'gatsby'
export default function(){
return <Head> Your head additions </Head>
} But maybe we should rethink the approach, and rather have to use something like this: // src/components/SEO.tsx
export function head(){
return <> Your head additions </>
} // src/pages/random-page.tsx
import { head } from '../components/SEO.tsx'
export { head }
export default function(){
return <Head> Your </Head>
} Surely, a // src/pages/layout.tsx
export function head(){
return <> Your head additions </>
}
// optional default export |
Beta Was this translation helpful? Give feedback.
-
One concern I have with this approach, is that any "post processing" that needs to be done on the query results etc to render the page & meta tags now needs to be done twice. While for many cases this would be a non-issue, for more complex pages it could nearly double hydration times. Might make sense as a separate RFC / project, but would a An alternate to this would be creating a resolver at the GraphQL layer and querying that, but I feel this is a much simpler and easy to understand solution. Writing customer resolvers in the GraphQL layer feels like a much more advanced feature than is justified for this use case. |
Beta Was this translation helpful? Give feedback.
-
Published in Gatsby 4.19: https://www.gatsbyjs.com/docs/reference/release-notes/v4.19/#gatsby-head-api |
Beta Was this translation helpful? Give feedback.
-
@mwskwong What makes structured data impossible? Google is happy, e.g. on my personal site where I use Gatsby Head: https://search.google.com/test/rich-results/result/r%2Fbreadcrumbs?id=rGYr6AS0tRo_JqhtMoCkTg But still, this should probably not be converted this way. |
Beta Was this translation helpful? Give feedback.
-
I have a couple of questions. How does the new Head API merge with onRenderBody function setHeadComponents? Does the Head API work alongside helmet? |
Beta Was this translation helpful? Give feedback.
-
Is it possible for me to access redux provider inside this component? |
Beta Was this translation helpful? Give feedback.
-
Yes, I think it does. I am currently using react-helmet so set the language of the pages with a lang attribute added to all pages, (e.g. ). Is it correct Gatsby Head does not have something equivalent? Should I be using another API so set this attribute? |
Beta Was this translation helpful? Give feedback.
-
Is there a way to access data from context providers that are used to wrap the pages via the E.g. when using a i18n library (e.g. |
Beta Was this translation helpful? Give feedback.
-
Hey all, apologies if I'm missing something easy, but migrating to the new API from react-helmet, it's not clear how to make it work with Jest tests. Typically, I just want to make sure the title and SEO meta are set correctly for a given page. With react-helmet, I had to mock it to render a simple version that bypassed using a static query, but in my page specs I was able to query the document.title and ensure it's correctness. With the new head-api, the head tag is always rendered empty by Jest so this approach no longer works. Suggestions? |
Beta Was this translation helpful? Give feedback.
-
I'm very happy to see the progress on this topic. I'm developing a little i18n plugin for Gatsby. How would you integrate the new Head API into a plugin? |
Beta Was this translation helpful? Give feedback.
-
Just wondering if it could be possible that if I define
the page does not render with some console error
If I remove the |
Beta Was this translation helpful? Give feedback.
-
How to migrate to Gatsby Head API without having in every page and template a head, even though they are all the same? I have layout components, which I use in pages and templates. Like a My questions are:
|
Beta Was this translation helpful? Give feedback.
-
I am currently sharing a const Layout = ({ children }) => {
const [theme, setTheme] = useState('light')
const onUpdateTheme = () => {
const newTheme = theme === 'dark' ? 'light' : 'dark'
window.localStorage.setItem('theme', newTheme)
setTheme(newTheme)
}
useEffect(() => {
const savedTheme = window.localStorage.getItem('theme')
if (savedTheme)
setTheme(savedTheme)
}, [])
return (
<div>
<Helmet>
<link rel="shortcut icon" type="image/png" href={favicon} />
{theme === 'dark' && (
<link rel="stylesheet" type="text/css" href="/dark-mode.css" />
)}
</Helmet>
<div id="layout" className="layout">
<Navigation onUpdateTheme={onUpdateTheme} theme={theme} />
<main>{children}</main>
<Footer />
</div>
</div>
)
} But with the implementation of Gatsby Head API, it has to be separated into two different exports. How do I share this state between the head and the body (using |
Beta Was this translation helpful? Give feedback.
-
Hi! I have a big Seo component already made in Helmet that pulls in a lot of data and makes the head completely managed by the SEO team in our CRM. I've tried to switch to an Head component instead of Helmet as I was using and it seems an impossible task as the component doesn't seem to accept static queries and I cannot query the same data on every single page to reduce duplication. |
Beta Was this translation helpful? Give feedback.
-
Made this plugin to use with Head API. https://github.com/cometkim/gatsby-plugin-head-seo |
Beta Was this translation helpful? Give feedback.
-
Is it possible to define a default |
Beta Was this translation helpful? Give feedback.
-
How to internationalize
Something like this doesn't work, as the Head component is not wrapped by any providers. |
Beta Was this translation helpful? Give feedback.
-
I'm trying to migrate a site with Head Api (from It's seems that the Head Api is not yet supported with
Is this feature on the roadmap ? Or now, should I also migrate my whole code from Node Api to File Api … ? Thanks. |
Beta Was this translation helpful? Give feedback.
-
I'm migrating a site from Site pages are in two languages and I want to set HTML attribute but for this site, I only use Gatsby with static generation, not SSR. Is it a workaround !? |
Beta Was this translation helpful? Give feedback.
-
What about ssr pages ? how do i get the server data within the head function? looks like i can only get: location, params, data, pageContext. |
Beta Was this translation helpful? Give feedback.
-
some things can be pushed into html.js
…On Sun, Jan 8, 2023, 11:22 PM Lennart ***@***.***> wrote:
Hey!
We hear you loud and clear about necessary improvements to the Head API,
it's on our radar. It's definitely not about that we *can't* do it,
between all the user requests you'd just have to prioritize your time
(small team, finite amount of time). And before EOY we spent it on #37069
<#37069>
Once we prioritized internally when to tackle this I'll let you know. In
the meantime, you can still use react-helmet
—
Reply to this email directly, view it on GitHub
<#35841 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AA4MU33HU256UVCBH547EJTWRO4BRANCNFSM5XZKURMA>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
HEAD cannot be used in mdx template. Is there any other way other than defining in mdx file directly? |
Beta Was this translation helpful? Give feedback.
-
Hey Everyone 👋 We've recently added a new feature that allows setting Additionally, we've wrapped |
Beta Was this translation helpful? Give feedback.
-
What is the suggestion for a plugin, which uses the Gatsby Head API? Currently I'm still relaying on |
Beta Was this translation helpful? Give feedback.
-
I'm currently updating my gatsby project, and trying to fix the Head API. The sad thing that I can't use pageContext in wrapRootElement has no access to pageContext, so I can't use react-intel's IntlProvider. After that I've almost pulled my hair out for another 2 hours, trying to debug something what actually is not broken. Somebody has monkeypatched console.error in and this just crashes my app in the place where console.error should behave correct, but now it does not! Please forbid monkeypatching!!! |
Beta Was this translation helpful? Give feedback.
-
The Head API uses the ReactContext of the wrapRootElement since version 5.6.0. Typically, the ThemeProviders for styling are also included here. This leads to duplicate <Style> codes in the head and body of the HTML page when generating the code. To avoid this, the ThemeProvider must be included in the wrapPageElement. In the browser, however, the entire theming is now recalculated with every page change, which cannot be intentional. What is the solution for this problem?
vs.
|
Beta Was this translation helpful? Give feedback.
-
Came here to verify Head still cannot be used in a component. It can't replace react-helmet if it requires duping code in every page. |
Beta Was this translation helpful? Give feedback.
-
Just wondering, why Gatsby Head renders a semi-duplicated tree of all high level components, so all wrapped with So in this code 'TopLayout re-rendered' would be printed twice if using Gatsby Head on page components and once if not. export const wrapRootElement: GatsbyBrowser["wrapRootElement"] = ({
element,
}) => <TopLayout>{element}</TopLayout>;
function TopLayout({children}: TopLayoutProps) {
console.log('TopLayout re-rendered');
return (
<ThemeProvider theme={theme}>
{children}
</ThemeProvider>
);
} |
Beta Was this translation helpful? Give feedback.
-
As written above, wrapRootElement is run twice if the Head API is used. Is this a bug or a feature? It should at least be documented. |
Beta Was this translation helpful? Give feedback.
Published in Gatsby 4.19: https://www.gatsbyjs.com/docs/reference/release-notes/v4.19/#gatsby-head-api