If you’re unsure of what TypeScript is, it’s basically a typed language built on top of JavaScript. All JavaScript is valid TypeScript, but not vice-versa. You can read more about it, and how it works with React/Next.js here. You can easily set up TypeScript in your project with just a few commands, and some minor refactoring, and in this article, I’ll tell you how!

Adding TypeScript

Initially adding TypeScript to your Next.js project is simple. Simply install TypeScript by running the following command:

npm install --save-dev typescript @types/react @types/node

And then create an empty tsconfig.json file at the root of your project. You can do this either manually, or by running the following command at the root of your project:

touch tsconfig.json

Then start your project using “npm run dev”, and you should see the following line in your console:

Next.js populates your tsconfig.json file with some pre-selected options. One I would recommend, but isn’t mandatory, is changing is setting strict to true on line 11, to enable some of TypeScript’s more strict typing options.

Refactoring

You will notice your project still runs fine, and this is because all of your .js or .jsx files are still JavaScript. To switch them to TypeScript, simply change the file extension to .ts or .tsx.

You may now be getting some more errors, so I’ll discuss some common ones. Here’s an example JavaScript component that retrieves some fun facts using an API call, and renders them with SSR. This component is using styling from TailwindCSS, which I highly recommend taking a look at.

import React, { useState } from 'react';
import axios from 'axios';

function fetchFact() {
    //Just a simple get request which gets back a fact in a JSON object
    return axios
        .get('https://uselessfacts.jsph.pl//random.json?language=en')
        .then((res) => {
            return res.data;
        });
}

//TS7031: Binding element 'facts' implicitly has an 'any' type.
export default function SSRExample({ facts }) {
    return (
        <div className="flex h-screen w-screen flex-col items-center justify-center bg-white text-3xl text-white">
            <p className="mb-10 rounded bg-neutral-800 p-10">
                Heres some fun facts!
            </p>
            <ul className="flex max-w-2xl flex-col gap-5 rounded bg-neutral-800 p-10 shadow">
                {
                    //TS7031: Binding element 'id' implicitly has an 'any' type.
                    //TS7031: Binding element 'text' implicitly has an 'any' type.
                    facts.map(({ id, text }) => (
                        <li key={id}>{text}</li>
                    ))
                }
            </ul>
        </div>
    );
}

//TS7031: Binding element 'query' implicitly has an 'any' type.
export async function getServerSideProps({ query }) {
    const { count } = query;
    const promises = [...new Array(Number(count))].map(() => fetchFact());

    const facts = await Promise.all(promises);
    return {
        props: {
            facts,
        },
    };
}

Typing Props

The first error I get with this component is that TypeScript doesn’t know what type my props are. This causes an error in strict mode, since we aren’t allowed to use any. To fix this, I just need to add a type for my props.

type Fact = {
    id: string;
    text: string;
};

type SSRExampleProps = {
    facts: Fact[];
};
//TS7031: Binding element 'facts' implicitly has an 'any' type.
export default function SSRExample({ facts }: SSRExampleProps) { ... }

Now the error from the initial props, and the error from trying to map the facts array is gone.

One important thing to note is that we’re using object destructuring here which may obscure the fact that props is one function parameter. Your props will always be an object containing whatever props you’re providing, instead of direct parameters.

export default function SSRExample({ facts }: { facts: { id: string; text: string }[]}) ✅
export default function SSRExample(facts: { id: string; text: string }[]) ❎

GetXProps

The other issue in this code is that our “getServerSideProps()” function has no idea what its parameters are meant to be. If you’re unfamiliar with this function, check out our article here for an introduction to Server Side Rendering. This will be the same if you’re using getStaticProps or getStaticPaths.

To fix this, Next.js provides types for each of these and their parameters. One important thing to note is that these types are for arrow functions, so you will have to refactor your function if you’re using a normal function body.

Here’s our updated getServerSideProps():

export const getServerSideProps: GetServerSideProps = async ({ query }) => {
    const { count } = query;
    const promises = [...new Array(Number(count))].map(() => fetchFact());

    const facts = await Promise.all(promises);
    return {
        props: {
            facts,
        },
    };
};

One more minor detail is that our fetchFact() function doesn’t have a type. With the way these Next.js functions are interacting, this isn’t causing any issues, but if I wished to use that function anywhere else I would run into a problem. Since we already made our type for the fact, refactoring this is simple:

function fetchFact(): Promise<Fact> {
    //Just a simple get request which gets back a fact in a JSON object
    return axios
        .get('https://uselessfacts.jsph.pl//random.json?language=en')
        .then((res) => {
            return res.data;
        });
}

State

Adding types to a useState() hook can be done by providing a type parameter to the function. I’ve refactored the previous example just to allow for clicking a button to retrieve an extra fact, and storing these extra facts in state.

export default function SSRExample({ facts }: SSRExampleProps) {
    const [extraFacts, setExtraFacts] = useState<Fact[]>([]);
    const getAnotherFact = () => {
        fetchFact().then((fact) => {
            setExtraFacts((oldValue) => [...oldValue, fact]);
        });
    };
    return (
        <div className="flex h-screen w-screen flex-col items-center justify-center bg-white text-3xl text-white">
            <p className="mb-10 rounded bg-neutral-800 p-10">
                Heres some fun facts!
            </p>
            <ul className="flex max-w-2xl flex-col gap-5 rounded bg-neutral-800 p-10 shadow">
                {
                    //TS7031: Binding element 'id' implicitly has an 'any' type.
                    //TS7031: Binding element 'text' implicitly has an 'any' type.
                    [...facts, ...extraFacts].map(({ id, text }) => (
                        <li key={id}>{text}</li>
                    ))
                }
                <li>
                    <button
                        className="rounded bg-neutral-900 p-5 shadow"
                        onClick={() => getAnotherFact()}>
                        More
                    </button>
                </li>
            </ul>
        </div>
    );
}

TypeScript will do its best to infer the type from your state’s initial value, but for more complex examples like this array of Facts, we have to include the type parameter.

Hopefully these examples were enough to help you get started with TypeScript in your Next.js project. TypeScript is an extremely useful language that helps you write cleaner, more correct code, so I highly recommend it! Feel free to comment as well if anything was confusing, if you have any questions, or if you simply enjoyed the article!

Avatar photo
👋 Hey, I'm Omari Thompson-Edwards
Hey, I'm Omari! I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, reach out on Twitter at @marile0n

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.

Comments

Hiya
I can start gradually chnaging various files here and there around app to typescript, with no issues ?
Once I change a file from js to ts, i must then refactor the file fully to be 100% typescript, and when save and run the project runs fine, just this one file will be executed/compiled with typescript ?