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
- Node.js (>= 10.13)
- 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'45interface BlogPostProps {6 post: Post<'title' | 'content' | 'excerpt' | 'readingTime'>7}89const BlogPost = ({ post }: BlogPostProps): ReactElement => {10 // ...11}1213export default BlogPost1415export 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 } = params23 const post = getPost(24 slug,25 ['title', 'content', 'excerpt', 'readingTime'],26 lang27 )2829 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'34export interface QParams extends ParsedUrlQuery {5 slug?: string6 lang?: Locale7}
... 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'34import { Post, getPost, getPosts } from '@api/posts'56// ...78export const getStaticProps: GetStaticProps<BlogPostProps, QParams> = async ({ params }) => {9 // ...10}1112export 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'34export type MyDocumentProps = DocumentInitialProps & {5 lang: Locale6}
🎉
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! 😄