Next.js dynamic routes & Typescript

How to avoid type errors when using dynamic routes on Next.js

J Paul Lescouzères
3 min readJun 11, 2021

Next.js’ routing system, based on the pages folder structure, is simple and powerful. It also allows us to create dynamic routes, based on query params (eg blog posts).

In this context, and using Typescript with types provided by Next.js, we end up though with a typing error when using the data fetching (getStaticProps or getServerSideProps) methods made available by the framework 😖.

How to solve this problem? Documentation is unfortunately not very explicit on this use case, and yet the solution is quite simple!

Based on a Next.js project, we will see how to adapt data fetching functions types, and thus avoid any error such as:

Property 'slug' does not exist on type 'ParsedUrlQuery | undefined'

Prerequisites

  1. Node.js (>= 10.13)
  2. A Next.js project (existing one or generated through npx create-next-app) with Typescript

This website was the starting point for this article, so do not hesitate to refer to its source code for more details!

Next.js types

Next.js provides specific functions for data fetching, whether we’re using static generation (SSG) or server side (SSR).

Let’s see here the case of static generation, with the getStaticProps and getStaticPaths functions (the logic is similar for the getServerSideProps method).

As the documentation says, Next.js provides dedicated types for each of these functions. However in the case of dynamic routes, the use of the type as indicated is not enough:

import type { GetStaticProps, GetStaticPaths } from 'next'
import { Post, getPost, getPosts } from '@api/posts'
interface BlogPostProps {
post: Post<'title' | 'content' | 'excerpt' | 'readingTime'>
}
const BlogPost = ({ post }: BlogPostProps): JSX.Element => {
// ...
}
export default BlogPostexport const getStaticProps: GetStaticProps = async ({ params }) => {
/*
Here is the error:
Properties 'slug' and 'lang' don't exist
on type 'ParsedUrlQuery | undefined'
*/
const { lang, slug } = params
const post = getPost(
slug,
['title', 'content', 'excerpt', 'readingTime'],
lang
)
return {
props: { post },
}
}

Fortunately, if we look at the types definitions for GetStaticProps and GetStaticPaths, via our IDE or the source code, we realize that they are in fact generic types, which we can therefore extend to make them aware of the specificities of our project! 🕺

Generics !

All that remains is to define a specific interface for the query parameters of our project, which will extend the basic ParsedUrlQuery interface provided by Next.js ...

import type { ParsedUrlQuery } from 'querystring'
import type { Locale } from './i18n'
export interface QParams extends ParsedUrlQuery {
slug?: string
lang?: Locale
}

… that we’ll add as an argument to the generic type of our function:

import type { GetStaticProps, GetStaticPaths } from 'next'
import type { QParams } from '@typings/global'
import { Post, getPost, getPosts } from '@api/posts'// ...export const getStaticProps: GetStaticProps<BlogPostProps, QParams> = async ({ params }) => {
// ...
}
export const getStaticPaths: GetStaticPaths<QParams> = async () => {
// ...
}

With some kinds of query parameters (like for this site, using a lang parameter for multilingual support), your _document.tsx file may also return a typing error... Here again, we'll just need to extend the interface initially provided by Next.js (DocumentInitialProps):

import type { DocumentInitialProps } from 'next/document'
import type { Locale } from './i18n'
export type MyDocumentProps = DocumentInitialProps & {
lang: Locale
}

🎉

Et voilà ! If the solution was (as often) in the source code, we can however regret that the official documentation does not mention this use case, however quite frequent…

Anyway, we can now easily adapt our typings to the specific needs of our project… Without forgetting the absolute rule that we perhaps forget too often: read the f*** manual! 😄

This article was originally published (in french & english) on my blog.

--

--