Next.js is an open-source framework designed to work with React. It is used to build landing pages, SEO-friendly websites, eCommerce stores, and all kinds of web applications that need fast, high-performance load times. On the other hand, TypeScript is a programming language built on JavaScript, which Next.js supports. These two combined provide a better experience both for the user and for the developer.

Next.js and TypeScript are primarily classified as full-stack frameworks and templating languages and extensions tools, respectively, but let’s take a look at what and how both are applied and how they can work together, including examples of its application.


Table of Contents

What is Next.js?
    ➤  What is next.js used for
    ➤  Client-side data fetching using Next.js
What is TypeScript?
    ➤  What do you use TypeScript for?
How do I install TypeScript in NextJS?


What is Next.js?

Next.js is an open-source framework created by Vercel. It claims to be the Web's Software Development Kit with all the tools needed "to make the Web. Faster" (sic). Learn about Next.js features with React and its applications here.

What is Next.js used for?

Next.js enables search engines to easily optimize React apps with zero configuration. A traditional React app is rendered on the client-side where the browser starts with a shell of an HTML page lacking any rendered content. From there, the browser fetches the JavaScript file containing the React code to render content to the page and make it interactive. However, there are two major drawbacks with client-side rendering:

1. The content is not reliably indexed by all search engines or read by social media link bots;
2. It can take longer to reach the first contentful paint when a user first lands on the web page.

Next.js is a framework that allows you to build a React app but render the content in advance on the server so the first thing a user or search bot sees is the fully rendered HTML. After receiving this initial page, client-side rendering takes over and it works just like a traditional React app. It’s the best of both worlds: fully rendered content for bots, and highly interactive content for users.

Client-side data fetching using Next.js

The real magic comes into play when we talk about data fetching because Next.js can perform multiple server rendering strategies from a single project. Client-side data fetching is useful when your page doesn't require SEO indexing, when you don't need to pre-render your data, or when the content of your pages needs to update frequently. Static generation or pre-rendering allows you to render your pages at build time. See the example below.

export default function IndexPage() {
  const [loading, setLoading] = useState(true);
  const [postList, setPostList] = useState([]);
  useEffect(() => {
    const fetchData = async () => {
      const res = await fetch(API_URL);
      const posts: IPost[] = await res.json();
      setPostList(posts);
      setLoading(false);
    };
    fetchData();
  }, []);
  //...

It is possible to see in the previous code snippet an example of Client-side data fetching using the useEffect hook of React.

First, we initialized constants to check if the fetching is still pending and to save the data resulting from the fetching process. Then, we call the useEffect hook with 2 different arguments:

  • Callback - function containing the side-effect login that runs right after the changes were being pushed to DOM. In our case, the logic is to fetch data from an endpoint and save it into our constant.
  • Dependencies - an array of dependencies that allows specifying when the callback should run. By passing an empty array, it means that we want the callback to run only once.

What is TypeScript?

TypeScript is a programming language that is developed and maintained by Microsoft, and it is a strict superset of JavaScript. It supports static and dynamic typing, and further provides inheritance features, classes, visibility scopes, namespaces, interfaces, unions, and other modern features. TS was designed to handle larger projects as it’s easier to refactor code. Learn more about its features with an in-depth comparison with JavaScript.

What do you use TypeScript for?

There are reasons galore why a JavaScrip developer considers using TypeScript:

  • Using new features of ECMAScript - TypeScript supports ECMAScript standards and transpile them to ECMAScript targets of your choice, so you can use features like modules, lambda functions, classes, restructuring, amongst others.

  • Static typing - JavaScript is a dynamical type and it does not know what type of variable is until it is actually instantiated at runtime; here, TypeScript adds type support to the JavaScript.

  • Type Inference - TypeScript makes typing a little bit easier and a lot less explicit by the usage of type inference. Even if you don’t explicitly type the types, they are still there to save you from doing something which otherwise would result in a runtime error.

  • Better IDE Support - The development experience with TypeScript is a great improvement over JavaScript. There is a wide range of IDEs that have excellent support for TypeScript, like the Visual Studio and VS Code, IntelliJ and Sublime, or WebStorm.

  • Strict Null Checking - Errors like “you cannot read a property ‘x’ of undefined” are common in JavaScript programming. You can avoid most of these kinds of errors through strict checking since one cannot use a variable that is not known to the TypeScript compiler.

  • Interoperability - TypeScript is closely related to JavaScript so it has great interoperability capabilities, but some extra work is required to work with the JavaScript libraries in TypeScript.

18 best Agile practices to use in your Software Development Cycle

How do I install TypeScript in NextJS?

Here’s a step-by-step guide to install TypeScript in a Next.js app:

1. Create base project - command: npx create-next-app next-app-example.
Create project with a base template.

2. Add tsconfig.json into the root of the project to active TypeScript.

3. Structure the project in the form.

src
├── components
|  ├── FormTodo.tsx
|  └── Todo.tsx
├── pages
   ├── Todo
        ├── index.tsx
|  ├── index.tsx
|  └── _app.tsx
├── styles
|  └── index.css
├── tsconfig.json
├── types
|  └── index.ts
├── next-env.d.ts

4. Create TypeScript types in Next.js

You can create types for anything in your application, including props types, API responses, arguments for functions, and so on.

We first create a type for our todos:

--code 

export interface ITodo {
  id: number;
  description: string;
  isDone: boolean;
}

--end code

5. Create components in Next.js

Now that we have our ITODO type, we can create the Todo.tsx component.

--code

import React from "react";
import { Button } from "react-bootstrap";
import { ITodo } from "../types";

type Props = {
  todo: ITodo;
  markTodo: (id: number) => void;
  removeTodo: (id: number) => void;
};

const Todo: React.FC<Props> = ({ todo, markTodo, removeTodo }) => {
  return (
    <div className="todo">
      <span style={{ textDecoration: todo.isDone ? "line-through" : "" }}>
        {todo.description}
      </span>
      <div>
        {!todo.isDone && (
          <Button variant="outline-success" onClick={() => markTodo(todo.id)}>
            ✓
          </Button>
        )}
        <Button variant="outline-danger" onClick={() => removeTodo(todo.id)}>
          ✕
        </Button>
      </div>
    </div>
  );
};

export default Todo;

--end code

As you can see, we start by importing the previous type we created, and also creating another one called Props, which will mirror the props received as parameters by the component.

This component is responsible for displaying the ITodo object. This component receives the ITodo object, a markTodo function, and a removeTodo function as props. Notice that this argument has to match the props type in order to make Typescript happy.

Now let's create our FormTodo.tsx component, responsible for adding Todos in our app.

--code
import * as React from "react";
import { Button, Form } from "react-bootstrap";

type Props = {
  addTodo: (text: string) => void;
};

const FormTodo: React.FC<Props> = ({ addTodo }) => {
  const [value, setValue] = React.useState<string>("");

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!value) return;
    addTodo(value);
    setValue("");
  };

  return (
    <Form onSubmit={handleSubmit}>
      <Form.Group>
        <Form.Label>
          <b>Add Todo</b>
        </Form.Label>
        <Form.Control
          type="text"
          className="input"
          value={value}
          onChange={(e) => setValue(e.target.value)}
          placeholder="Add new todo"
        />
      </Form.Group>
      <Button variant="primary mb-3" type="submit">
        Submit
      </Button>
    </Form>
  );
};

export default FormTodo;

--end code

Our component accepts the addTodo as a parameter. It handles the submission of a new Todo. If the value is not empty, then we call the addTodo function on that todo text and then set the value of the form to empty again. This component returns a form that accepts todos and has a submit button, which by clicking the button, we add the todo in the todo list.

6. Create our page in order to use our react components.
We will start by importing the components and types we created earlier.

-- code
import React from "react";
import { Card } from "react-bootstrap";
import { InferGetStaticPropsType } from "next";
import { ITodo } from "../../types";
import Todo from "../../components/Todo";
import FormTodo from "../../components/FormTodo";

export default function indexPage({
  todos,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  const [todosList, setTodosList] = React.useState(todos);

  const addTodo = (text: string) => {
    const newTodo: ITodo = {
      id: Math.random(),
      description: text,
      isDone: false,
    };
    const newTodos = [...todosList, newTodo];
    setTodosList(newTodos);
  };

  const markTodo = (id: number) => {
    const newTodos = todosList.map((todo) => {
      if (todo.id === id) {
        return {
          ...todo,
          isDone: true,
        };
      }
      return todo;
    });
    setTodosList(newTodos);
  };

  const removeTodo = (id: number) => {
    const newTodos = todosList.filter((todo) => {
      return todo.id !== id;
    });
    setTodosList(newTodos);
  };

  return (
    <div className="app">
      <div className="container">
        <h1 className="text-center mb-4">Todo List</h1>
        <FormTodo addTodo={addTodo} />
        <div>
          {todosList.map((todo, index) => (
            <Card key={index}>
              <Card.Body>
                <Todo todo={todo} markTodo={markTodo} removeTodo={removeTodo} />
              </Card.Body>
            </Card>
          ))}
        </div>
      </div>
    </div>
  );
}

export async function getStaticProps() {
  const todos: ITodo[] = [
    {
      id: 2,
      description: "Test next.js app",
      isDone: true,
    },
    {
      id: 1,
      description: "Build next.js app",
      isDone: true,
    },
  ];

  return {
    props: {
      todos,
    },
  };
}
--end code

After importing the components and types, we imported InferGetStaticPropsType, which is provided by Next.js allowing us to set the type on the method getStaticProps.

After that we initialized our todoList using the useState hook, passing as an argument our initial todos provided by the getStaticProps.

Finally, we declared our main fuctions that implement our logic:

  • addTodo - allows to add a todo in our list
  • removeTodo - allows to remove a todo in our list
  • markTodo - allows to set a todo as done in our list

In the end, we return a list of our todos, using our components.


New call-to-action

Found this article useful? You might like these ones too!