How To Set Up a Gatsby Project with TypeScript

The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

Introduction

TypeScript is a superset of JavaScript that adds optional static typing at build time, which cuts down on debugging runtime errors. It has grown into a powerful alternative to JavaScript over the years. At the same time, Gatsby has emerged as a useful front-end framework for creating static websites. TypeScript’s static-typing abilities go well with a static-site generator like Gatsby, and Gatsby has built-in support for coding in TypeScript.

In this tutorial, you’re going to use Gatsby’s built-in capabilities to configure a Gatsby project for TypeScript. After this tutorial, you will have learned how to integrate TypeScript into your Gatsby project.

Prerequisites

  • You will need to have both Node and npm installed in order to run a development environment and handle TypeScript- or Gatsby-related packages, respectively. This tutorial was tested with Node.js version 14.13.0 and npm version 6.14.8. To install on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.
  • To create a new Gatsby project, you will need the Gatsby CLI command line tool installed on your computer. To set this up, follow Step 1 in How to Set Up Your First Gatsby Site. This step will also show you how to create a new Gatsby project with the gatsby new command.
  • You will need some familiarity with GraphQL queries and using GraphiQL to query for local image data. If you’d like a refresher on the query sandbox in GraphiQL, read How to Handle Images with GraphQL and the Gatsby Image API.
  • You will need sufficient knowledge of JavaScript, especially ES6+ syntax such as destructuring and imports/exports. You can find more information on these topics in Understanding Destructuring, Rest Parameters, and Spread Syntax in JavaScript and Understanding Modules and Import and Export Statements in JavaScript.
  • Since Gatsby is a React-based framework, you will be refactoring and creating components in this tutorial. You can learn more about this in How to Create Custom Components in React.
  • Additionally, you will need TypeScript installed on your machine. To do this, refer to the official TypeScript website. If you are using an editor besides Visual Studio Code, you may need to go through a few extra steps to make sure you have TypeScript performing type-checks at build time and showing any errors. For example, if you’re using Atom, you’ll need to install the atom-typescript package to be able to achieve a true TypeScript experience. If you would like to download TypeScript only for your project, do so after the Gatsby project folder has been set up.

Step 1 — Creating a New Gatsby Site and Removing Boilerplate

To get started, you’re going to create your Gatsby site and make sure that you can run a server and view the site. After that, you will remove some unused boilerplate files and code. This will set your project up for edits in later steps.

Open your computer’s console/terminal and run the following command:

  • gatsby new gatsby-typescript-tutorial

This will take a few seconds to run as it sets up the necessary boilerplate files and folders for the Gatsby site. After it is finished, cd into the project’s directory:

  • cd gatsby-typescript-tutorial

To make sure the site’s development environment can start properly, run the following command:

  • gatsby develop

After a few seconds, you will receive the following message in the console:

Output... You can now view gatsby-starter-default in the browser.    http://localhost:8000 

Usually, the default port is :8000, but you can change this by running gatsby develop -p another_number instead.

Head over to your preferred browser and type http://localhost:8000 in the address bar to find the site. It will look like this:

Gatsby Default Starter Site

Next, you’ll remove all unnecessary files. This includes gatsby-node.js, gastby-browser.js, and gatsby-ssr.js:

  • rm gatsby-node.js
  • rm gastby-browser.js
  • rm gatsby-ssr.js

Next, to finish setup, you’re going to remove some boilerplate code from your project’s index page. In your project’s root directory, head to the src directory, followed by pages and then open the index.js file.

For this tutorial, you are only going to work with an <Image /> component, so you can delete code related to the <Link /> component, along with the h1 and p elements. Your file will then look like the following:

gatsby-typescript-tutorial/src/pages/index.js

import React from "react"  import Layout from "../components/layout" import Image from "../components/image" import SEO from "../components/seo"  const IndexPage = () => (   <Layout>     <SEO title="Home" />     <div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>       <Image />     </div>   </Layout> )  export default IndexPage 

Save and close the file.

Now that you’ve created your project and completed some initial setup, you are ready to install the necessary plugins.

Step 2 — Installing Dependencies

In order to set up support for TypeScript in Gatsby, you’ll need some additional plugins and dependencies, which you will install in this step.

The gatsby-plugin-typescript plugin already comes with a newly created Gatsby site. Unless you want to change any of its default options, you don’t have to add this plugin to your gatsby-config.js file explicitly. This Gatsby plugin makes writing .ts and .tsx files in TypeScript possible.

Since your app can read TypeScript files, you will now change Gatsby’s JavaScript files to a TypeScript file extension. In particular, change header.js, image.js, layout.js, and seo.js in src/components and index.js in src/pages to header.tsx, image.tsx, layout.tsx, seo.tsx, and index.tsx:

  • mv src/components/header.js src/components/header.tsx
  • mv src/components/image.js src/components/image.tsx
  • mv src/components/layout.js src/components/layout.tsx
  • mv src/components/seo.js src/components/seo.tsx
  • mv src/pages/index.js src/pages/index.tsx

You are using the mv command to rename the files to the second argument. .tsx is the file extension, since these files use JSX.

There is one important caveat about the gatsby-plugin-typescript plugin, however: it doesn’t include type-checking at build time (a core function of TypeScript). If you’re using VS Code, this won’t be an issue because TypeScript is a supported language in Visual Studio. But if you’re using another editor, like Atom, you will need to do some extra configurations to achieve a full TypeScript development experience.

Since Gatsby is a React-based framework, adding some additional React-related typing is also recommended. To add type-checking for types specific to React, run the following command:

  • npm add @types/react

To add type-checking for types related to the React DOM, use this command:

  • npm add @types/react-dom

Now that you’ve become familiar with the plugin gatsby-plugin-typescript, you are ready to configure your Gatsby site for TypeScript in the next step.

Step 3 — Configuring TypeScript for Gatsby with the tsconfig.json File

In this step, you will create a tsconfig.json file. A tsconfig.json file has two primary purposes: establishing the root directory of the TypeScript project (include) and overriding the TypeScript compiler’s default configurations (compilerOptions). There are a couple of ways to create this file. If you have the tsc command line tool installed with npm, you could create a new tsconfig file with tsc --init. But the file is then populated with many default options and comments.

Instead, create a new file at the root of your directory (gatsby-typescript-project/) and name it tsconfig.json.

Next, create an object with two properties, compilerOptions and include, populated with the following code:

gatsby-typescript-tutorial/tsconfig.json

 {   "compilerOptions": {     "module": "commonjs",     "target": "es6",     "jsx": "preserve",     "lib": ["dom", "es2015", "es2017"],     "strict": true,     "noEmit": true,     "isolatedModules": true,     "esModuleInterop": true,     "skipLibCheck": true,     "noUnusedLocals": true,     "noUnusedParameters": true,     "removeComments": false   },   "include": ["./src/**/*"] } 

Note:
This configuration is partially based on the gatsby-starter-typescript-plus starter.

Save this file and close it when you are done.

The include property points to an array of filenames or paths that the compiler knows to convert from TypeScript to JavaScript.

Here is a brief explanation of each option used in compilerOptions:

  • module – Sets the module system for the project; commonjs is used by default.
  • target – Depending on what version of JavaScript you’re using, this option determines which features to downlevel and which to leave alone. This can be helpful if your project is deployed to older environments vs. newer environments.
  • jsx – Setting for how JSX is treated in .tsx files. The preserve option leaves the JSX unchanged.
  • lib – An array of specified type-definitions of different JS libraries/APIs (dom, es2015, etc.).
  • strict – When set to true, this enables TypeScript’s type-checking abilities at build-time.
  • noEmit – Since Gatsby already uses Babel to compile your code to readable JavaScript, you change this option to true to leave TypeScript out it.
  • isolatedModules – By choosing Babel as your compiler/transpiler, you are opting for compilation one file at a time, which may cause potential problems at runtime. Setting this option to true allows TypeScript to warn you if you are about to run into this problem.
  • esModuleIterop – Enabling this option allows your use of CommonJS (your set module) and ES modules (importing and exporting custom variables and functions) to better work together and allow namespace objects for all imports.
  • noUnusedLocals and noUnusedParamters – Enabling these two options disables the errors TypeScript would normally report if you were to create an unused local variable or parameter.
  • removeComments – Setting this to false (or not setting it at all) allows there to be comments present after any TypeScript files have been converted to JavaScript.

You can learn more about these different options and many more by visiting TypeScript’s reference guide for tsconfig.

Now that TypeScript is configured for Gatsby, you are going to complete your TypeScript integration by refactoring some of your boilerplate files in src/components and src/pages.

Step 4 — Refactoring seo.tsx for TypeScript

In this step, you’re going to add some TypeScript syntax to the seo.tsx file. This step goes in depth to explain some concepts of TypeScript; the next step will show how to refactor other boilerplate code in a more abbreviated manner.

One feature of TypeScript is its flexibility with its syntax. If you don’t want to add typing to your variables explicitly, you don’t have to. Gatsby believes that adopting TypeScript in your workflow “can and should be incremental”, and so this step will concentrate on three core TypeScript concepts:

  • basic types
  • defining types and interfaces
  • working with build-time errors

Basic Types in TypeScript

TypeScript supports basic datatypes including: boolean, number, and string. The major syntactical difference with TypeScript, compared to JavaScript, is that variables can now be defined with an associated type.

For example, the following code block shows how to assign the basic types with the highlighted code:

let num: number; num = 0  let str: string; str = "TypeScript & Gatsby"  let typeScriptIsAwesome: boolean; typeScriptIsAwesome = true; 

In this code, num must be a number, str must be a string, and typeScriptIsAwesome must be a boolean.

Now you will examine the defaultProps and propTypes declarations in the seo.tsx file, found in the src/components directory. Open the file in your editor and look for the following highlighted lines:

gatsby-typescript-tutorial/src/components/seo.tsx

... import React from "react" import PropTypes from "prop-types" import { Helmet } from "react-helmet" import { useStaticQuery, graphql } from "gatsby"  ...       ].concat(meta)}     />   ) }   SEO.defaultProps = {   lang: `en`,   meta: [],   description: ``, }  SEO.propTypes = {   description: PropTypes.string,   lang: PropTypes.string,   meta: PropTypes.arrayOf(PropTypes.object),   title: PropTypes.string.isRequired, }  export default SEO 

By default, a Gatsby site’s SEO component comes with a weak typing system using PropTypes. The defaultProps and propTypes are explicitly declared, using the imported PropsTypes class. For example, in the meta prop (or alias) of the propTypes object, its value is an array of objects, each of which is itself a prop of the PropTypes component. Some props are marked as required (isRequired) while others are not, implying they are optional.

Since you are using TypeScript, you will be replacing this typing system. Go ahead and delete defaultProps and propTypes (along with the import statement for the PropTypes at the top of the file). Your file will look like the following:

gatsby-typescript-tutorial/src/components/seo.tsx

 ... import React from "react" import { Helmet } from "react-helmet" import { useStaticQuery, graphql } from "gatsby"   ...       ].concat(meta)}     />   ) }  export default SEO 

Now that you’ve removed the default typing, you’ll write out the type aliases with TypeScript.

Defining TypeScript Interfaces

In TypeScript, an interface is used to define the “shape” of a custom type. These are used to represent the value type of complex pieces of data like React components and function parameters. In the seo.tsx file, you’re going to build an interface to replace the defaultProps and propTypes definitions that were deleted.

Add the following highlighted lines:

gatsby-typescript-tutorial/src/components/seo.ts

 ... import React from "react" import { Helmet } from "react-helmet" import { useStaticQuery, graphql } from "gatsby"  interface SEOProps {   description?: string,   lang?: string,   meta?: Array<{name: string, content: string}>,   title: string }  ...   

The interface SEOProps accomplishes what SEO.propTypes did by setting each of the properties associated data type as well as marking some as required with the ? character.

Typing a Function

Just like in JavaScript, functions play an important role in TypeScript applications. You can even type functions by specifying the datatype of the arguments passed into them. In the seo.tsx file, you will now work on the defined SEO function component. Under where the interface for SEOProps was defined, you’re going to explicitly declare the type of the SEO component’s function arguments, along with a return type of SEOProps right after:

Add the following highlighted code:

gatsby-typescript-tutorial/src/components/seo.ts

... interface SEOProps {   description?: string,   lang?: string,   meta?: Array<{name: string, content: string}>,   title: string }  function SEO({ description='', lang='en', meta=[], title }: SEOProps) {   ... } 

Here you set defaults for the SEO function arguments so that they adhere to the interface, and added the interface with : SEOProps. Remember that you at least have to include the title in the list of arguments passed to the SEO component because it was defined as a required property in the SEOProps interface you defined earlier.

Lastly, you can revise the metaDescription and defaultTitle constant declarations by setting their type, which is string in this case:

gatsby-typescript-tutorial/src/components/seo.tsx

 ... function SEO({ description='', lang='en', meta=[], title }: SEOProps) {   const { site } = useStaticQuery(     graphql`       query {         site {           siteMetadata {             title             description             author           }         }       }     `   )    const metaDescription: string = description || site.siteMetadata.description   const defaultTitle: string = site.siteMetadata?.title ... 

Another type in TypeScript is the any type. For situations where you’re dealing with a variable whose type is unclear or difficult to define, use any as a last resort to avoid any build-time errors.

An example of using the any type is when dealing with data fetched from a third-party, like an API request or a GraphQL query. In the seo.tsx file, where the destructured site property is defined with a GraphQL static query, set its type to any:

gatsby-typescript-tutorial/src/components/seo.tsx

... interface SEOProps {   description?: string,   lang?: string,   meta?: Array<{name: string, content: string}>,   title: string }  function SEO({ description='', lang='en', meta=[], title }: Props) {   const { site }: any = useStaticQuery(     graphql`       query {         site {           siteMetadata {             title             description             author           }         }       }     `   )   ... } 

Save and exit the file.

It’s important to always keep the defined values consistent with their type. Otherwise, you will see build-time errors appear via the TypeScript compiler.

Build-Time Errors

It will be helpful to become accustomed to the errors TypeScript will catch and report at build-time. The idea is that TypeScript catches these errors, mostly type-related, at build-time, and this cuts down on the amount of debugging in the long run (in compile-time).

One example of a build-time error occurring is when you declare a variable of one type but assign it a value that is of another type. If you were to change the value of one of the keyword arguments passed to the SEO component to one of a different type, the TypeScript compiler will detect the inconsistency and report the error. The following is an image of what this looks like in VSCode:

A build-time error in VSCode when the description variable is set to a number.

The error says Type 'number' is not assignable to type 'string'. This is because, when you set up your interface, you said the description property would be of type string. The value 0 is of type number. If you change the value of description back into a “string”, the error message will go away.

Step 5 — Refactoring the Rest of the Boilerplate

Lastly, you will refactor the remaining boilerplate files with TypeScript: layout.tsx, image.tsx, and header.tsx. Like seo.tsx, these component files are located in the src/components directory.

Open src/components/layout.tsx. Towards the bottom, is the defined Layout.propTypes. Delete the following highlighted lines:

gatsby-typescript-tutorial/src/components/layout.tsx

 import React from "react" import PropTypes from "prop-types" import { useStaticQuery, graphql } from "gatsby" ...  Layout.propTypes = {   children: PropTypes.node.isRequired, }  export default Layout 

The children prop shows that its value is of type node per the PropTypes class. Plus, it’s a required prop. Since the children in the layout could be anything from simple text to React child components, use ReactNode as the associated type by importing near the top and adding it to the interface:

Add the following highlighted lines:

gatsby-typescript-tutorial/src/components/layout.tsx

... import React, { ReactNode } from "react" import { useStaticQuery, graphql } from "gatsby"  import Header from "./header" import "./layout.css"  interface LayoutProps {   children: ReactNode }  const Layout = ({ children }: LayoutProps) => {   ... 

Next, add a type to the data variable that stores a GraphQL query that fetches site title data. Since this query object is coming from a third-party entity like GraphQL, give data an any type. Lastly, add the string type to the siteTitle variable that works with that data:

gatsby-typescript-tutorial/src/components/layout.tsx

 ... const Layout = ({ children }: LayoutProps) => {   const data: any = useStaticQuery(graphql`   query MyQuery {     site {       siteMetadata {         title       }     }   } `)  const siteTitle: string = data.site.siteMetadata?.title || `Title`    return (     <>       <Header siteTitle={siteTitle} />       <div ... 

Save and close the file.

Next, open the src/components/image.tsx file.

Here you are dealing with a similar situation as layout.tsx. There is a data variable that stores a GraphQL query that could have an any type. The image fluid data that is passed into the fluid attribute of the <Img /> component could be separated from the return statement into its own variable. It’s also a complex variable like data, so give this an any type as well:

gatsby-typescript-tutorial/src/components/image.tsx

... const Image = () => {   const data: any = useStaticQuery(graphql`     query {       placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {         childImageSharp {           fluid(maxWidth: 300) {             ...GatsbyImageSharpFluid           }         }       }     }   `)    if (!data?.placeholderImage?.childImageSharp?.fluid) {     return <div>Picture not found</div>   }    const imageFluid: any = data.placeholderImage.childImageSharp.fluid    return <Img fluid={imageFluid} /> }  export default Image 

Save and close the file.

Now open the src/components/header.tsx file. This file also comes with predefined prop types, using the PropTypes class. Like seo.tsx, image.tsx, and layout.tsx, replace Header.defaultProps and Header.propTypes with an interface using the same prop names:

gatsby-typescript-tutorial/src/components/header.tsx

import { Link } from "gatsby" import React from "react"  interface HeaderProps {   siteTitle: string }  const Header = ({ siteTitle }: HeaderProps) => (   <header     style={{       background: `rebeccapurple`,       marginBottom: `1.45rem`,     }}   >     <div       style={{         margin: `0 auto`,         maxWidth: 960,         padding: `1.45rem 1.0875rem`,       }}     >       <h1 style={{ margin: 0 }}>         <Link           to="/"           style={{             color: `white`,             textDecoration: `none`,           }}         >           {siteTitle}         </Link>       </h1>     </div>   </header> )  export default Header 

Save and close the file.

With your files refactored for TypeScript, you can now restart the server to make sure everything is working. Run the following command:

  • gatsby develop

When you navigate to localhost:8000, your browser will render the following:

Gatsby Default Development page

Conclusion

TypeScript’s static-typing capabilities go a long way in keeping debugging at a minimum. It’s also a great language for Gatsby sites since it’s supported by default. Gatsby itself is a useful front-end tool for creating static-sites, such as landing pages.

You now have two popular tools at your disposal. To learn more about TypeScript and all you can do with it, head over to the official TypeScript handbook.