How to translate the Gatsby SEO Component to TypeScript
Tags: gatsby, seo, typescript
Published: 2021-03-28 20:58:36

---
title: How to translate the Gatsby SEO Component to TypeScript
tags: Gatsby, SEO, TypeScript
---
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

```jsx
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.

```js
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:

```ts
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<T> Generic here.

We see that the objects inside the meta array are expected in two possible versions:

#### Version 1

```js
{
  name: "***NAME***"
  content: "***CONTENT***"
}
```

#### Version 2

```js
{
  property: "***PROPETY***"
  content: "***CONTENT***"
}
```

We can describe these objects via types:

```ts
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:

```ts
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:

```tsx
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.

```bash
yarn add @types/react-helmet -D
```

or

```bash
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

```ts
type QueryTypes = {
  site: {
    siteMetadata: {
      title: string
      description: string
      author: string
    }
  }
}
```

Provide it to the useStaticQuery call and enjoy the features TypeScript holds for you.

```tsx
// 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 ;)