Routes dynamiques avec Next.js & Typescript
Publié il y a 11 mois~3 min
Le système de routing de Next.js, basé sur la structure du dossier pages
, est à la fois simple et puissant. Il nous permet par ailleurs de créer des routes dynamiques, basées sur des query params (ex: articles de blog).
Dans ce contexte, et en utilisant Typescript avec les types fournis par Next.js, nous nous retrouvons pourtant avec une erreur de typage lorsque l'on utilise les méthodes de data fetching (getStaticProps
ou getServerSideProps
) mises à disposition par le framework 😖.
Comment résoudre ce problème ? La documentation n'est malheureusement pas très explicite sur ce cas d'usage, et pourtant la solution est relativement simple !
Sur la base d'un projet Next.js, nous allons voir comment adapter le typage des méthodes de data fetching de Next.js, et ainsi éviter toute erreur de ce type :
1Property 'slug' does not exist on type 'ParsedUrlQuery | undefined'
Pré-requis
- Node.js (>= 10.13)
- Un projet Next.js (existant ou généré via
npx create-next-app
) avec Typescript
Ce site web a servi de base à cet article, n'hésitez donc pas à vous référer au code source pour plus de détails !
Les types Next.js
Next.js met à disposition des fonctions spécifiques pour la récupération de données, que l'on soit en génération statique (SSG) ou côté serveur (SSR).
Voyons ici le cas de la génération statique, avec les fonctions getStaticProps
et getStaticPaths
(la logique est similaire pour la méthode getServerSideProps
).
Comme l'indique la documentation, Next.js met à disposition des types dédiés à chacune de ces fonctions. Pourtant dans le cas des routes dynamiques, l'utilisation du type tel qu'indiqué dans la documentation ne suffit pas :
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}
Fort heureusement, si l'on examine les définitions des types GetStaticProps
et GetStaticPaths
, via notre IDE ou le code source, on se rend compte qu'il s'agit en fait de types génériques, que l'on peut donc étendre pour leur donner connaissance des spécificités de notre projet ! 🕺
Génériques !
Il ne reste donc plus qu'à définir une interface spécifique aux paramètres de requêtes de notre projet, qui étendra l'interface de base ParsedUrlQuery
, fournie par Next.js...
1import type { ParsedUrlQuery } from 'querystring'2import type { Locale } from './i18n'34export interface QParams extends ParsedUrlQuery {5 slug?: string6 lang?: Locale7}
... que l'on ajoutera comme argument au type générique de notre fonction :
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}
Dans certains cas de paramètres de requêtes (comme pour ce site, utilisant un paramètre lang
pour la gestion du multilingue), il est probable que le typage de votre fichier _document.tsx
retourne lui aussi une erreur... Là encore, il suffira d'étendre l'interface initialement fournie par Next.js (DocumentInitialProps
) :
1import type { DocumentInitialProps } from 'next/document'2import type { Locale } from './i18n'34export type MyDocumentProps = DocumentInitialProps & {5 lang: Locale6}
🎉
Et voilà ! Si la solution se trouvait (comme souvent) dans le code source, on peut toutefois regretter que la documentation officielle ne fasse pas mention de cet usage, pourtant assez fréquent...
Quoi qu'il en soit, nous pouvons désormais adapter facilement nos typages aux besoins spécifiques de notre projet... Sans oublier la règle absolue que l'on oublie peut-être trop souvent : read the f*** manual! 😄