pages Next.js directement en markdown

Avec Next.js, chaque page du site est représentée par un fichier javascript qui lui sert de point d'entrée. Ces pages sont ensuite pré-rendues à la compilation, ou côté serveur si elles sont dynamiques, ce qui a l'avantage d'être plus facilement indexé par les moteurs de recherche et d'être plus rapide à afficher par le client puisque l'affichage se fait avant qu'aucun code n'ai besoin d'être exécuté.

Tout cela est formidable, mais je ne veux évidemment pas avoir à écrire mes articles en javascript ! J'écris mes articles en Markdown car c'est un excellent compromis entre lisibilité et richesse de formatage.

Ce que j'ai fini par faire est d'écrire un plugin webpack qui me permet d'utiliser des fichiers markdown à la place des fichiers javascript. Le plugin prend le contenu d'un fichier .md en entrée et génère du javascript que next.js convertie ensuite comme d'habitude. Techniquement, il m'a suffit de modifier next.config.js pour effectuer cette conversion:

config.module.rules.push({
  test: /\.md$/,
  include: pagesFolder,
  use: [
    ...loaders,
    {
      loader:path.resolve('lib/post-loader.js'),
      options:{}
    }
  ]
})
  • pagesFolder est le chemin vers le dossier /pages que next.js utilise pour former la structure ; les fichiers markdown présent ailleurs ne doivent pas passer par ce plugin
  • loaders est la liste de plugins déjà enregistrés pour traiter les fichiers javascript, il doit y avoir moyen de rendre cela implicite mais je n'ai pas encore trouvé comment faire (les conseils sont bienvenus)

Au moment de la compilation, le code dans lib/post-loader.js prend le contenu markdown et l'insère dans le code suivant :

import PostPage from '~/components/post'
import {getPostThread} from '~/lib/compile-posts'
<<<importSupportScript(md)>>>

const Page = (props)=> (
  <PostPage
    slug={<<<slug>>>}
    content={props.content}
    script={MDScript}
    threadPosts={props.threadPosts}
    ${coverSize(md)}
  />
)

export async function getStaticProps(context) {
  return {
    props: {
      content: <<<escapedSource>>>,
      threadPosts: getPostThread('posts',<<<thread>>>,['slug', 'title', 'date'])
    },
  }
}

export default Page
  • escapedSource est le contenu markdown sous forme d'une chaîne de caractère constante javascript.
  • slug est le nom du fichier markdown
  • thread est le nom de la série de post, si précisé dans le front matter de l'article
  • coverSize(md) récupère les dimensions de l'image de couverture s'il y en a une dans le front matter
  • importSupportScript(md) me permet d'importer du code (en tant que MDScript) dans le cas des pages qui comportent des fonctionnalités particulière, mais j'y reviendrais dans un prochain article.

Dans une précédente version de cet article, je disais qu'écrire ces articles fonctionne efficacement comme une revue de code... et je ne croyais pas si bien dire. J'avais une vision faussée de getStaticProps, je cherchais à l'éviter et générer l'ensemble des données directement depuis mon plugin en espérant simplifier le code généré, ce qui fonctionne, mais ça n'est pas la meilleure stratégie comme j'ai fini par me rendre compte en travaillant sur la traduction...

Pour voir tout cela en contexte, rendez vous sur gitHub !

Dans de prochains articles, je parlerais de mon implémentation de i18n (internationalisation) qui est presque terminée et, comme je le disais plus haut, je parlerais aussi de comment mes articles comportant des fonctionnalités particulières sont constitués.

Jérôme Muffat-Méridol
27 janvier 2021

Behind the scenes

  1. open-sourcing myself
  2. using Markdown as Next.js pages
  3. Posting both in French and English