Next.js dynamic routes & Typescript

Publié il y a 11 mois~3 min

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:

1Property '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:

1import type { ReactElement } from 'react'
2import type { GetStaticProps, GetStaticPaths } from 'next'
3import { Post, getPost, getPosts } from '@api/posts'
4
5interface BlogPostProps {
6 post: Post<'title' | 'content' | 'excerpt' | 'readingTime'>
7}
8
9const BlogPost = ({ post }: BlogPostProps): ReactElement => {
10 // ...
11}
12
13export default BlogPost
14
15export const getStaticProps: GetStaticProps = async ({
16 params,
17}) => {
18 /*
19 Here is the error:
20 Properties 'slug' and 'lang' don't exist on type 'ParsedUrlQuery | undefined'
21 */
22 const { lang, slug } = params
23 const post = getPost(
24 slug,
25 ['title', 'content', 'excerpt', 'readingTime'],
26 lang
27 )
28
29 return {
30 props: { post },
31 }
32}

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 ...

1import type { ParsedUrlQuery } from 'querystring'
2import type { Locale } from './i18n'
3
4export interface QParams extends ParsedUrlQuery {
5 slug?: string
6 lang?: Locale
7}

... that we'll add as an argument to the generic type of our function:

1import type { GetStaticProps, GetStaticPaths } from 'next'
2import type { QParams } from '@typings/global'
3
4import { Post, getPost, getPosts } from '@api/posts'
5
6// ...
7
8export const getStaticProps: GetStaticProps<BlogPostProps, QParams> = async ({ params }) => {
9 // ...
10}
11
12export const getStaticPaths: GetStaticPaths<QParams> = async () => {
13 // ...
14}

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):

1import type { DocumentInitialProps } from 'next/document'
2import type { Locale } from './i18n'
3
4export type MyDocumentProps = DocumentInitialProps & {
5 lang: Locale
6}

🎉

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! 😄