Ever since I consider myself a TypeScript fanboy, when starting a new Gatsby Project with gatsby-starter-default, or similar, I've always had the problem of how to type the SEO Component from Gatsby.
At the beginning of my TypeScript journey, I did the "easy" thing - Setting allowJS: true
in my tsconfig and leaving the SEO Component as is... But we all know, this is not a satisfying way of solving problems, so I took the time and want to share my results with you.
TL;DR
import React from "react"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
export function SEO({
description = "",
lang = "en",
meta = [],
title,
}: SEOProps) {
const { site } = useStaticQuery<QueryTypes>(SEOStaticQuery)
const metaDescription = description || site.siteMetadata.description
const defaultTitle = site.siteMetadata?.title
return (
<Helmet
htmlAttributes={{
lang,
}}
title={title}
titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : undefined}
meta={[
{
name: `description`,
content: metaDescription,
},
{
property: `og:title`,
content: title,
},
{
property: `og:description`,
content: metaDescription,
},
{
property: `og:type`,
content: `website`,
},
{
name: `twitter:card`,
content: `summary`,
},
{
name: `twitter:creator`,
content: site.siteMetadata?.author || ``,
},
{
name: `twitter:title`,
content: title,
},
{
name: `twitter:description`,
content: metaDescription,
},
].concat(meta)}
/>
)
}
// Types
type SEOProps = {
description?: string
lang?: string
meta?: Meta
title: string
}
type Meta = ConcatArray<PropertyMetaObj | NameMetaObj>
type PropertyMetaObj = {
property: string
content: string
}
type NameMetaObj = {
name: string
content: string
}
type QueryTypes = {
site: {
siteMetadata: {
title: string
description: string
author: string
}
}
}
// Queries
const SEOStaticQuery = graphql`
query {
site {
siteMetadata {
title
description
author
}
}
}
`
The Props
Let's start with the JS-Version of the component, where the gatsby developers left us a pretty good description of how the props should look like by providing propTypes and defaultProps.
SEO.defaultProps = {
lang: `en`,
meta: [],
description: ``,
}
SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired,
}
Our TypesScript type would look like this:
type SEOProps = {
title: string
description?: string
lang?: string
meta?: Meta
}
Since title
is the only propType using the TYPE.isRequired
property we add a ?
to all other keys in our type-object to make them optional.
The Meta Prop
We see the meta-prop is passed through to the Helmet component by using the Array.concat()
method to add our values from the meta prop to some defaults. You would assume that the meta prop is just typed as Array
, but TypeScript is throwing an error here when passing meta to Helmet. Luckily TypeScript exactly tells you what is expected here:
We can use the ConcatArray Generic here.
We see that the objects inside the meta array are expected in two possible versions:
Version 1
{
name: "***NAME***"
content: "***CONTENT***"
}
Version 2
{
property: "***PROPETY***"
content: "***CONTENT***"
}
We can describe these objects via types:
type NameMetaObj = {
name: string
content: string
}
type PropertyMetaObj = {
property: string
content: string
}
Now we can describe our meta property as a ConcatArray Generic with the Union Type of NameMetaObj and PropertyMetaObj as its argument.🧐 In TypeScript, this reads like this:
type Meta = ConcatArray<NameMetaObj | PropertyMetaObj>
Default Props
Using defaultProps in React/TypeScript is as easy as setting them inside the props destructuring in the component definition:
export function SEO({
description = "",
lang = "de",
meta = [],
title,
}: SEOProps) {
// Component
}
React Helmet Types
At this point, you may be finished, but in case you haven't installed @types/react-helmet
yet, your editor may yell something about not using Helmet as a JSX.Component at you. Just install react-helmet types package and you're good to go.
yarn add @types/react-helmet -D
or
npm install @types/react-helmet --save-dev
Note: If your editor still yells at you, make sure your TypeScript configuration (tsconfig.json e.g.) includes all @types packages.
The Query
We can even get fancier and type the static query because useStaticQuery provides the ability to describe the expected results
type QueryTypes = {
site: {
siteMetadata: {
title: string
description: string
author: string
}
}
}
Provide it to the useStaticQuery call and enjoy the features TypeScript holds for you.
// Inside the Component
const { site } = useStaticQuery<QueryTypes>(SEOStaticQuery)
Wrap Up
That's it for now, feel free to reach out if I missed anything. Please note that this is my first blog post ever... So be kind ;)
Top comments (0)