Ibrahima Ndaw JavaScript enthusiast, full-stack developer, and blogger who also dabbles in UI/UX design.

Using Next.js with TypeScript

13 min read 3683

Using Next.js With TypeScript

Editor’s note: This guide to using Next.js with TypeScript was last updated on 24 April 2023 to reflect recent updates and add sections to clarify why you should use Next.js with TypeScript, how to add TypeScript to existing projects, and how to make your types global. To learn more, refer to our guide on types vs. interfaces in TypeScript.

Next.js allows you to build static and dynamic (server-side) apps using React. It ships with handy functionalities such as API routes, file-system routing, image optimization, middleware, ES Modules support and URL imports, server components, HTTP streaming, and Next.js Live.

Next.js is built with TypeScript under the hood, so you get better IntelliSense and type definitions in your editor by default with just JavaScript. But, when you couple that with TypeScript, you can get an even better developer experience — including instant feedback when your component expects props but you didn’t pass any.

You’re also able to build with Next’s exported types and define your own to build with across your applications. These types help give your code better structure by dictating what your objects, arrays, etc., look like ahead of time. That way, you, your code editor, and any developer after you know how to reference your code. Next.js’ features make building full-stack React apps easier than ever, from simplified routing to performance-enhancing features like image optimization.

In this tutorial, we’ll demonstrate how to use Next.js with TypeScript and introduce you to an exciting and modern stack for building high-quality, search-optimized, and predictable apps. To show Next.js and TypeScript in action, we’ll walk through how to build a simple article manager app. Our example app will retrieve data from JSON placeholder. We’ll cover the following in detail:

What is Next.js?

Next.js is a production-ready framework built on top of React and Node.js. It ships with all the features listed above and more. You can use Next.js to build static and dynamic apps since it supports client and server-side rendering. Next.js v9 introduced API routes, which allow you to extend your Next app with a real backend (serverless) built with Node.js, Express.js, GraphQL, and so on.

Next.js uses automatic code-splitting (lazy loading) to render only the JavaScript needed for your app. Next.js can also pre-render your pages at build time to serve on demand, which can make your app feel snappy because the browser does not have to spend time executing the JavaScript bundle to generate the HTML for your app. This makes it possible for more search engine crawlers to index your app, which is great for SEO.

What is TypeScript?

TypeScript is a popular language created and maintained by Microsoft. It’s a superset of JavaScript, which means all valid JavaScript is valid TypeScript. You can convert your existing JavaScript app to TypeScript, and it should work as expected, as long as your code is valid JavaScript. TypeScript allows you to set types on your variables and functions so you can type-check your code statically and catch errors at compile time.

You can also use modern features that are not yet supported in JavaScript. And, don’t worry about browser support — TypeScript compiles to plain JavaScript, which means your TypeScript code will never ship in the browser.

Why should you use Next.js with TypeScript?

Using Next.js with TypeScript is a powerful combination that offers a range of benefits for developers, ultimately enhancing their experience and the quality of the applications they create. Here’s why you should consider using Next.js with TypeScript, accompanied by some feelings and examples.

Improved code quality and maintainability

TypeScript adds static types to JavaScript, significantly reducing the chances of introducing bugs and making the code more maintainable. When working with Next.js, TypeScript’s type-checking capabilities enable developers to feel more confident in their code, as they can catch potential errors early in the development process. Imagine you have a blog application that receives data from an API. With TypeScript, you can create interfaces for the expected data structure, ensuring that the received data matches the expected format, and that any discrepancies are caught during development.

Better developer experience

TypeScript’s static types and Next.js’s built-in features, like hot reloading and automatic routing, work together to provide a seamless and enjoyable developer experience. With TypeScript, developers can feel a sense of relief when working with complex applications, as the type information guides them through the code, making it easier to understand and navigate.

For example, when using TypeScript with Next.js, features like autocompletion and type inference in code editors like VS Code significantly improve the developer experience. This assistance can boost productivity, as developers spend less time figuring out the correct syntax or searching for the right function.


Both Next.js and TypeScript are designed to support large-scale applications, making them a perfect match for growing projects. TypeScript’s type system enforces a SOLID structure that promotes best practices, making it easier to scale the codebase without compromising on maintainability or quality. Additionally, Next.js offers features like automatic code-splitting and server-side rendering, which help build high-performing applications.

Suppose you are working on a large ecommerce application with multiple developers contributing to the project. With TypeScript and Next.js, it becomes easier to manage the growing complexity of the codebase, as the enforced structure and built-in features ensure that the application remains performant and maintainable.

Ecosystem and community support

Next.js and TypeScript enjoy strong community support, with a wealth of resources, libraries, and third-party tools available to developers. This thriving ecosystem makes it easy to find solutions to common problems and integrate with other technologies. Many popular libraries, such as Material UI, TanStack Query, and styled-components, provide TypeScript typings inbuilt or via @types packages. This support enables developers to quickly integrate these libraries into their Next.js projects, ensuring type safety and a more robust application.

Using TypeScript in a Next.js app

To create a new Next.js app, you can use Create Next App. Begin by opening your CLI and running the command below:

npx create-next-app next-typescript-example

The above command will take you through the following prompts:

  • ✔ Would you like to use TypeScript with this project? … No / Yes: Because we are uisng TypeScript for this app, we’ll select Yes
  • ✔ Would you like to use ESLint with this project? … No / Yes: Select Yes to use ESlint
  • ✔ Would you like to use Tailwind CSS with this project? … No / Yes: **Select Yes to use Tailwind CSS for styling the application
  • ✔ Would you like to use src/ directory with this project? … No / Yes ?: **We’ll select Yes to use the src folder in the application
  • ✔ Would you like to use experimental app/ directory with this project? › No /Yes ?: **For this tutorial, we’ll stick to the traditional pages folder for this application
  • ✔ What import alias would you like configured? … @/*?: Enter @/* to use the import alias to import modules into the application

Here’s what it should look like:

Example of the Next.js CLI

After the above prompts, the command will generate a fresh Next.js app and install the required dependencies. Next, change the directory into the project folder with the command below:

cd next-typescript-example

Now, let’s structure the project as follows:

├── components
|  ├── AddPost.tsx
|  └── Post.tsx
├── pages
|  ├── index.tsx
|  └── _app.tsx
|  └── _document.tsx
├── styles
|  └── globals.css
├── tsconfig.json
├── types
|  └── globals.d.ts
|  └── index.ts
├── next-env.d.ts
└── package.json

Because we chose to use TypeScript for this application during the project setup, a tsconfig.json file has been created in the root of the project. Next.js will recognize the file and use TypeScript for the project. With this in place, we can now create files with .ts or .tsx extensions. Next.js handles the compilation of the TypeScript code to JavaScript and then serves our app as usual in the browser.

Adding TypeScript to an existing project

Before we create our Next.js application, let’s look at how you can add TypeScript to your existing Next.js project. First, install the required dependencies with the command below:

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

Now, let’s look at what each of these dependencies does:

  • typescript: This is the main TypeScript package that adds TypeScript support to your project
  • @types/react: This package provides TypeScript type definitions for React. It allows you to use TypeScript with React and ensures that your code is type-safe
  • @types/node: This package provides TypeScript type definitions for Node.js. It allows you to use TypeScript with Node.js and ensures that your code is type-safe

Next, create a tsconfig.json file in the root directory of your project that will contain the TypeScript configurations for your project. Add the configurations below to the tsconfig.json file:

  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]

Let me clarify what these configurations do:

  • target: This specifies the version of JavaScript that the TypeScript compiler should output. In this case, it is set to ES5
  • lib: Specifies the libraries that are available to your code. In this case, it includes the DOM API, iterable objects, and the latest version of ECMAScript
  • allowJs: This specifies whether the TypeScript compiler should allow JavaScript files
  • skipLibCheck: This specifies whether the TypeScript compiler should skip type checking of declaration files
  • esModuleInterop: This specifies whether the TypeScript compiler should allow default imports from modules with no default export
  • allowSyntheticDefaultImports: This specifies whether the TypeScript compiler should allow default imports of modules that do not have a default export
  • strict: This specifies whether the TypeScript compiler should enforce strict type checking
  • forceConsistentCasingInFileNames: This specifies whether the TypeScript compiler should enforce consistent casing of filenames
  • module: This specifies the module format that the TypeScript compiler should use. In this case, it is set to ES modules
  • moduleResolution: This specifies the algorithm that the TypeScript compiler should use to resolve module dependencies. In this case, it is set to node to use Node.js module resolution
  • resolveJsonModule: This specifies whether the TypeScript compiler should allow importing JSON files
  • isolatedModules: This specifies whether each file should be treated as a separate module
  • noEmit: This specifies whether the TypeScript compiler should emit any output files

The include and exclude options in the tsconfig.json file determine which files should be included or excluded from the TypeScript compilation process. In this case, we have configured the compiler to include all .ts and .tsx files in the project while excluding the node_modules directory. Also, the next-env.d.ts file is a specific file that Next.js uses to declare global types for the project, and it is included as a part of the TypeScript compilation process.

Next, update the content of the file next.config.js file to use TypeScript with the code snippet below:

module.exports = {
  webpack(config) {
    config.resolve.extensions.push('.ts', '.tsx');
    return config;

This configuration tells Next.js to resolve files with the .ts and .tsx extensions. Finally, name all your .jsx and .js files to .ts, and .ts files and update your code to TypeScript syntax.

Creating TypeScript types in Next.js

Types are crucial in TypeScript. They provide a solid foundation for maintainable code, catching errors, and improving the developer experience. With custom types in Next.js, you’ll harness TypeScript’s full potential, resulting in more reliable code. And, depending on the use case, you can create types for anything in your application, including prop types, API responses, arguments for your utility functions, and even properties of your global state! First, let’s create a type for our posts. The interface below reflects the shape of a Post object. It expects id, title, and body properties:

// types/index.ts
export interface IPost {
  id: number
  title: string
  body: string

However, this method may not be handy because we’ll import our components each time we need to use it. So, let’s learn how to make our types global.

How to make your types global

Creating global types in our Next.js application simplifies the work, and you can use them throughout the project without importing them in each file. It feels seamless to access these types, which cover various application aspects like prop types, API responses, or global state properties.

First, create a types directory in the root of your Next.js project, create a types directory. Then, inside the types directory, create a new file with a global.d.ts extension, such as global.d.ts. This file will hold all your global type declarations. Then, add the IPost type to the global.d.ts file, to declare a global interface for a post with the code snippet below:

// types/global.d.ts

export {}

declare global {
  interface IPost {
    id: number
    title: string
    body: string

In the above code, we employed the export {} line within our global.d.ts file, designating it as an external module. This approach allows us to efficiently augment the global scope.

Finally, update tsconfig.json to inform TypeScript about the global type declaration file, as shown below:

  "compilerOptions": {
    "typeRoots": ["./types", "./node_modules/@types"]

Creating components in Next.js

Now that the post type (IPost) is ready for use, let’s create the React components and set the types, as shown below:

// components/AddPost.tsx

import * as React from 'react'
type Props = {
  savePost: (e: React.FormEvent, formData: IPost) => void
const AddPost: React.FC<Props> = ({ savePost }) => {
  const [formData, setFormData] = React.useState<IPost>()
  const handleForm = (e: React.FormEvent<HTMLInputElement>): void => {
    setFormData((prevState) => ({
      [e.currentTarget?.id]: e.currentTarget?.value,
    } as IPost));
  return (
    <form className='Form' onSubmit={(e) => savePost(e, formData as IPost)}>
        <div className='Form--field'>
          <label htmlFor='name'>Title</label>
          <input onChange={handleForm} type='text' id='title' />
        <div className='Form--field'>
          <label htmlFor='body'>Description</label>
          <input onChange={handleForm} type='text' id='body' />
        disabled={formData === undefined ? true : false}
        Add Post
export default AddPost

As you can see, we are using the IPost type without importing it into the file. After that, we create another type named Props that mirrors the props received as a parameter by the component. Next, we set the type IPost on the useState Hook. Then, we use it to handle the form data.

Once the form is submitted, we rely on the function savePost to save the data on the array of posts. Now, we can create and save a new post. Let’s move on to the component responsible for displaying the Post object. Refer to the following code:

// components/Post.tsx

import * as React from 'react'

type Props = {
  post: IPost
  deletePost: (id: number) => void

const Post: React.FC<Props> = ({ post, deletePost }) => {
  return (
    <div className='Card'>
      <div className='Card--body'>
        <h1 className='Card--body-title'>{post.title}</h1>
        <p className='Card--body-text'>{post.body}</p>
      <button className='Card__button' onClick={() => deletePost(post.id)}>

export default Post

This Post component receives the post object to show and a deletePost function as props. The arguments have to match the Props to make TypeScript happy. We are now able to add, show, and delete posts. Let’s import the components into the index.tsx file and create the logic to handle the posts:

import * as React from 'react'
import AddPost from '@/components/AddPost'
import Post from '@/components/Post'
import { InferGetStaticPropsType } from 'next'

const API_URL: string = 'https://jsonplaceholder.typicode.com/posts'

export default function Home({
}: InferGetStaticPropsType<typeof getStaticProps>) {
  const [postList, setPostList] = React.useState(posts)
  const addPost = async (e: React.FormEvent, formData: IPost) => {
    const post: IPost = {
      id: Math.random(),
      title: formData.title,
      body: formData.body,
    setPostList([post, ...postList])
  const deletePost = async (id: number) => {
    const posts: IPost[] = postList.filter((post: IPost) => post.id !== id)
  if (!postList) return <h1>Loading...</h1>
  return (
    <main className='container'>
      <h1>My posts</h1>
      <AddPost savePost={addPost} />
      {postList.map((post: IPost) => (
        <Post key={post.id} deletePost={deletePost} post={post} />
export async function getStaticProps() {
  const res = await fetch(API_URL)
  const posts: IPost[] = await res.json()
  return {
    props: {

In this component, we use the types and import the components created earlier. The type InferGetStaticPropsType, provided by Next.js, allows us to set the type on the method getStaticProps. It will infer the type defined on the props returned by getStaticProps.

After that, we initialize the state with the posts array using the useState Hook. Next, we declare the function addPost to save the data on the array of posts. The deletePost method receives as an argument the id of the post, which allows us to filter the array and remove the post. Finally, we pass in the expected props to the components.

Then, we loop through the response data and display it using the Todo component. The data is retrieved from the JSON placeholder API with the help of the getStaticProps method provided by Next.js. Setting the type of posts to be an array of objects that have the structure defined by IPost helps us and our editor know exactly what fields are available to us from the API’s response.

You can alternatively use the getServerSideProps method, Fetch, or a library to fetch the data. It’s just a matter of how you want to render your Next.js app. In this demo, we render our app by statically generating the pages, which means Next.js generates HTML files with little JavaScript at build time, and the same HTML file is served on each request. Statically generating your pages is the recommended way to serve your app because of the performance benefits of serving pre-generated HTML files.

Server-side rendering is also an option; this method generates a fresh HTML file whenever a request is made to the server. This is the mode you would use getServerSideProps for.

Add styling to the app

Now, let’s add some styling to the application to make it virtually appealing to the users. To that, copy the styles here to the styles/globals.css file.

Testing your Next.js app

With this final touch, the app is ready to be tested on the browser. Begin by locating the root of the project and running this command:

yarn dev

Or, if using npm:

npm run dev

If everything works as expected, you should see the Next app at http://localhost:3000/, as shown below:

Using Next.js With TypeScript Demo

That’s it!

Implementing TypeScript in Next.js API routes

To implement TypeScript in Next.js API routes, we need to create our API routes in the pages/api directory. For example, let’s create a simple API route that returns a JSON response with a message:

// pages/api/hello.ts

import { NextApiRequest, NextApiResponse } from 'next'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json({ message: 'Hello, world!' })

In the above code snippet, we’ve imported NextApiRequest and NextApiResponse types from next, which provide type checking for the request and response objects. We’ve also added a type annotation to the handler function, indicating that it expects a NextApiRequest object as its first parameter and a NextApiResponse object as its second parameter.

Benefits of using TypeScript with Next.js

Using TypeScript with Next.js brings numerous advantages, making your development journey more enjoyable and productive. First, TypeScript’s static typing system helps you catch errors early, making it easier to maintain and refactor your codebase. It feels reassuring to have the compiler notify you of potential issues before they become problems in production.

With TypeScript, you’ll benefit from better autocompletion, code navigation, and refactoring features in modern IDEs. It’s a delight to work with code that’s easier to understand and navigate. TypeScript also ensures that your code adheres to the defined types, making your application more reliable and less prone to runtime errors. This added security brings peace of mind, as you know your application is better protected against unforeseen issues.

For instance, imagine you’re building a Next.js application that retrieves data from a REST API and want to display a list of products. With TypeScript, you can define a Product interface, ensuring that your components consistently handle product data:

typescriptCopy code
interface Product {
  id: number;
  name: string;
  description: string;
  price: number;

Using this Product interface in your components and API calls ensures that your application always expects the correct data structure. This makes it easier to catch any discrepancies between the expected data and what the API returns, increasing the overall reliability of your application.


In this tutorial, we covered how to use TypeScript with Next.js by building an article manager app. You can view the finished project on GitHub. Overall, Next.js has good support for TypeScript and is easy to set up. That makes building strongly typed React apps with Next.js and TypeScript that run on either the client or the server simple. Simply put, Next.js and TypeScript is a very exciting stack to try on your next React project.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Ibrahima Ndaw JavaScript enthusiast, full-stack developer, and blogger who also dabbles in UI/UX design.

One Reply to “Using Next.js with TypeScript”

  1. This is good but how can we define types globally so that we don’t need to keep importing them each time?

Leave a Reply