X

Comment j’ai construit mon blog en utilisant MDX, Next.js et React


Si vous avez pensé à créer un blog de développement pour vous-même, vous avez probablement été un peu dépassé par le nombre d’outils et de technologies. Nous vivons dans une ère d’abondance, et il y a beaucoup d’options.

Lorsque je construisais ce blog, ma plus grande priorité était de trouver une solution qui me permettrait d’intégrer contenu totalement personnalisé dans chaque message, comme cette chose d’animation de logo qui explose. Lorsque vous utilisez du démarquage ou un éditeur de texte enrichi dans un CMS, la manière de procéder n’est pas du tout claire : vous êtes généralement limité à la poignée d’éléments HTML que ces outils peuvent afficher.

Dans cet article, je vais décomposer le fonctionnement de mon blog, afin que vous puissiez créer quelque chose de similaire pour vous-même. Je couvrirai également toutes les questions les plus fréquemment posées que j’ai reçues au fil des ans. Ce n’est pas un tutoriel, mais il devrait vous donner une feuille de route générale à suivre.

Ce blog est une application Next.js.

Avec Next, vous disposez de plusieurs options en matière de rendu de page : vous pouvez choisir de le faire “à la demande” (rendu côté serveur) ou à l’avance (génération de site statique). J’ai choisi de créer tous les articles de blog à l’avance, lorsque le site est généré.

J’utilise également les API Routes de Next pour les choses qui nécessitent de la persistance dans le backend. J’utilise MongoDB comme base de données, pour stocker des éléments tels que le nombre de likes de chaque publication.

Je déploie ce blog sur Vercel. Je les ai initialement choisis parce qu’ils sont la société derrière Next.js, et j’ai pensé que ce serait bien optimisé. Honnêtement, leur plateforme est géniale. J’ai également déplacé certains de mes projets non-Next là-bas.

En ce qui concerne le style, j’utilise des composants de style et j’écris tous les styles à partir de zéro. Je n’utilise aucune bibliothèque “cosmétique” comme Bootstrap (et je ne pense pas que vous devriez non plus). J’utilise cependant l’interface utilisateur Reach pour des choses comme les modaux.

Pour l’animation, je m’appuie principalement sur React Spring, bien que j’aie récemment commencé à m’intéresser à Framer Motion.

Mais la partie la plus critique de ma pile est MDX.

MDX est une extension de Markdown qui vous permet d’importer et d’utiliser des composants React personnalisés.

Même si vous n’avez jamais écrit Markdown, vous l’avez probablement déjà vu. C’est un format largement utilisé — tous ces README.md les fichiers affichés sur les référentiels Github sont Markdown !

Voici à quoi ressemble Markdown :

When using Markdown in a web application, there’s a “compile” step; the Markdown needs to be transformed into HTML, so that it can be understood by the browser. Those asterisks get turned into a <strong> tag, the list gets turned into a <ul>, and each paragraph gets a <p> tag.

This is great, but it means we’re limited to a handful of HTML elements that Markdown is aware of.

MDX takes the format a step further, and allows us to include our own elements, in the form of React components:

We can create our own rich set of primitives, and use them in our content. On this blog, for example, I’m not limited to italic and bold text; I also have spicy text and .

We can also create custom one-off widgets. In “A Friendly Introduction to Spring Physics”, an article all about motion and animation, I wanted readers to be able to play and experiment with the physics. So I created a bespoke React component, SpringMechanism:

(Drag and release the weight! Alternatively, you can also focus it and press “Space”.)

When it came to explaining technical elements, like the effects of tension or mass on the physics, I added props to this component, to change how it behaves. This side-by-side demo shows how mass affects the animation:

This is so much more powerful than describing the physics in words, or showing the effects with a video. By giving the reader control, we flip from passive learning to active learning.

If you’re a React developer, hopefully this is giving you a ton of ideas. Literally anything you do in a React app can now be embedded anywhere inside your blog posts!

You might be wondering: why not create a “regular” React application, and render each post as its own route? Why bother with MDX at all?

When I first started my blog, this is exactly what I did. It worked OK, but MDX is better for two reasons:

  1. The authoring experience is so much nicer. I don’t need to wrap each paragraph in a <p> tag. I can use asterisks and underscores for strong/em tags. It’s delightful.

  2. Even more importantly, Markdown is data. I can extract metadata from each post, so that I can show a filtered, sorted list of posts on the homepage, or all the posts about CSS on the CSS category page. I wouldn’t be able to do this as easily if each post was its own React component.

In my mind, MDX is the perfect sweet spot between “all-code” (a standard React application) and “all-data” (formatted text stored in a CMS).

It’s not the only way to accomplish this, but it’s the lowest-friction way I’ve found for developers working on a solo blog.

In the future, I plan on writing an “Intro to MDX” blog post. For now, though, I’ll defer to these amazing community resources:

As I write this, there are four (4) different popular ways to use MDX with Next.js 😅

There’s:

On this blog, I use next-mdx-enhanced, but I mainly chose it because I was migrating this blog from Gatsby to Next, and it was the option with the least amount of friction.

On my course platform, I use next-mdx-remote. Version 3.0 just shipped, and it improves/fixes a bunch of stuff.

The biggest remaining drawback is that you can’t import one-off components inside MDX files. Every bespoke component you create must be packed together inside one monolithic MDX bundle. In order to avoid an enormous bundle filesize, I need to make heavy use of lazy-loading, which adds an annoying bit of friction to the developer experience, and worsens the user experience.

Going forward, I’ll probaby use mdx-bundler. It offers many of the benefits of next-mdx-remote, but without the “no import” drawback.

In addition to the content itself, we need a way to store “metadata” — stuff like the title, the abstract, the publication date.

On my blog, I use frontmatter. Frontmatter is a Markdown addon that lets us define key-value pairs at the top of the document.

Here’s what this post looks like:

The mechanism for defining and accessing metadata will vary depending on your MDX tool. In my case, layout refers to the React page component that will be used (Article). When this post is rendered, the Article component will be passed two props: frontMatter and children:

In this example, I’ll wind up with an h1 that reads “How I Built my Blog”, followed by the article content.

Link to this heading

Index pages

On the homepage of this blog, I have two separate lists of posts:

  1. The 20 latest posts, in chronological order

  2. The 10 most-viewed posts of all time

Using the method getStaticProps, Next allows us to do some work when the site is built, before it gets deployed. I calculate the lists of posts to be displayed in these sections during that time.

Here’s what that looks like:

getLatestContent is a method that traverses the local filesystem to find all of the .mdx blog posts. The logic looks something like this:

  1. Collect all of the MDX files in the pages directory, using fs.readdirSync.

  2. Load the frontmatter (I use an NPM package for this, gray-matter).
  3. Filter out any unpublished posts (ones where isPublished is not set to true).

  4. Sort all of the blog posts by publishedOn, and slice out everything after the specified limit.

  5. Return the data.

This feels surprisingly low-level, especially when coming from Gatsby (where all this data was available magically through GraphQL). Ultimately, though, I kinda like it. It’s definitely more work, but it gives me a ton of control.

This control comes in handy when it comes to the other method listed here, getPopularContent. It’s very similar to getLatestContent, but it makes a database request (I rely on the hit-counter data stored in MongoDB to determine popularity).

This blog is mostly a static site, but there are some data-driven aspects. For example, each article comes with a 90s-inspired hit counter!

I’ve previously written about how I built my hit counter, using Netlify Functions and Gatsby. The process is similar with Next and Vercel API routes. You can learn more about it in the official docs.

I use a similar mechanism for the “Like” counter, the cute heart-shaped button that lets readers inflate my ego.

Each user is allowed to “like” each post up to 16 times. Initially, I tracked this in localStorage, but one of my favourite people on Twitter showed me why this is a bad idea by adding almost 40k fake likes to a post.

Fortunately, Vercel includes the user’s IP address in the request headers. When a user clicks the heart, I hash their IP address (to protect privacy) and check to make sure they haven’t already hit the limit.

In my database, I have a big map for each post, tracking likes by user:

It’s hard to overstate how powerful API routes are. This blog is a static site with relatively modest backend needs, but I’m using the same stack on my course platform, a full-fledged dynamic web application, with user authentication and roles and transactional email and all sorts of stuff. It holds up super well.

As I’ve mentioned, I migrated my site from Gatsby to Next.js. I did this primarily because I was sick of context-switching: I built a course platform using Next, and wanted both projects to use the same stack.

Because Next is relatively unopinionated when it comes to app structure and data, there isn’t the same rich ecosystem of plugins. Before, I had been generating an RSS feed and a sitemap from a handy-dandy Gatsby plugin. As part of the migration, I had to create my own versions of them.

Both of these tasks are beyond the scope of this article, but I’ll share the general idea.

First, I added a folder called build-helpers. It includes a handful of Node scripts that perform specific operations. I run them before I build the Next.js site:

Quick little NPM protip: you can create pre-run scripts by using the pre prefix. When I run npm run build or yarn build, it will automatically run the prebuild script first, if defined. You can also run scripts afterwards with the post prefix.

Taking the RSS feed as an example, the process looks like this:

  1. Install the rss dependency: It’ll handle the XML formatting for us.

  2. Collect all of the MDX files in the pages directory, using fs.readdirSync.

  3. Load the frontmatter, using gray-matter. Pluck out the relevant details (title, abstract).

  4. Filter out any unpublished posts (ones where isPublished is not set to true).

  5. Add each item to the feed, using the rss module

  6. Save the generated .xml file in ./public/rss.xml

By storing it in public, I ensure that Next will copy it over to the static directory, and make it publicly-available. I also added this file to my .gitignore, since it’s a generated file.

A bunch of folks have asked me how I created this little fella:

I wish I could take credit for it, but I didn’t create it. I commissioned an artist to do it for me. We created several facial expressions, and two lighting modes (try toggling between light/dark mode using the toggle in the top-right!). Overall cost was around US$500, I believe.

Aside from that, I designed/built everything else you see on this blog. Design doesn’t come naturally to me, but I’ve learned a few tricks over the years:

  1. When I’ve worked with designers, I’ve tried to learn from them. I asked questions like “how did you come up with this layout?” or “Why is this heading this color?”. You can build a design intuition by trying to understand the rules and systems behind the designs you implement.
  2. For years, I never actually came up with anything from scratch. I’d search sites like dribbble and find 4-5 “references”. I’d take the layout from one, the color scheme from another, the typography and spacing from a third. Learning to skillfully combine existing designs takes some practice, but it’s a heck of a lot faster than learning to create compelling designs from scratch.
  3. There’s a bit of a curse when it comes to design: if you spend 4 hours building something, you lose all objective perception. It’s impossible to tell if it’s good or not. For this reason, I always step away from a project for a day or two after I have a rough design in place. When I come back, I’ll be able to tell if it’s good or not.

My goal isn’t to become a world-class designer — that would be the work of a lifetime, and an entirely separate career! But by putting in a bit of work and taking a few shortcuts, I’ve become competent enough at design to feel good about the things I build.

A lot of developers believe that you need to have some intrinsic artistic talent to become good at design, and I know that it’s not true, because I’m a terrible artist 😅 If you’re interested in becoming a better designer, be sure to join my newsletter—I’ll be going much deeper into this stuff in the future.

No developer blog is complete without syntax-highlighted code snippets. I have a couple different options for that on this blog.

Many Markdown processors allows us to create code samples with triple backticks (```). We can specify the language for syntax highlighting as well (```css).

With MDX, I map that syntax to a specific component, StaticCodeSnippet. It produces blocks like this:

Under the hood, this uses prism-react-renderer with a custom syntax theme.

Sometimes, though, I want the code to be “live-editable”, and to showcase the result of that code. This is useful when I want to encourage the reader to experiment with the code, to learn how it works.

In those situations, I have a different component, <Playground>. It looks like this:

Code Playground

Résultat

Actualiser le volet des résultats

Pour construire ce composant, j’ai forké Playground d’agneym. C’est un petit utilitaire fantastique. J’ai fait quelques ajustements assez substantiels, principalement au niveau des cosmétiques et de la convivialité (la logique de rendu sous-jacente est pratiquement inchangée).

Voici à quoi cela ressemble dans le MDX :


Ce n’est pas le meilleur expérience de création : il n’y a pas de coloration syntaxique et l’indentation est géniale. Je finis souvent par écrire le code dans le terrain de jeu lui-même, puis le copie dans la source.

Un piège est que MDX n’aime pas avoir des lignes vides au milieu des éléments React. Dans l’extrait ci-dessus, je veux une ligne vide entre les deux règles CSS. Si je laisse une ligne vierge réelle, MDX explose avec un message d’erreur difficile à déchiffrer. Nous pouvons ajouter une nouvelle ligne explicite avec \n. Malheureusement, cela crée deux lignes vides, puisque l’explicite \n est immédiatement suivi d’un saut de ligne réel. Alors j’échappe au saut de ligne avec \n\.

Parlons de certains des autres éléments moins qu’idéaux.

Voici donc une erreur que j’ai honte d’admettre avoir commise plus d’une fois : parfois, mes messages “tout nouveaux” auront une date de “dernière mise à jour” datant d’il y a plusieurs mois/années.

Voici pourquoi cela se produit : chaque article de blog a un publishedOn date dans le frontmatter, et éventuellement une updatedOn date également. Lorsque je commence à écrire un nouveau message, je copie/colle un ancien message au hasard pour me donner la structure du frontmatter. Si je ne me souviens pas explicitement de mettre à jour la date lorsque je publie le message (ou chaque fois que je le mets à jour), la mauvaise date s’affiche.

Dans un monde idéal, updatedOn peut être dérivé automatiquement, en fonction de la date de la dernière modification du fichier. Les systèmes d’exploitation peuvent savoir quand le fichier a été mis à jour pour la dernière fois, je devrais donc pouvoir récupérer ces informations à partir du système d’exploitation lors de la création du site.

Malheureusement, cela ne fonctionne pas : mon site n’est pas construit sur ma machine locale, il est construit sur les serveurs de Vercel. Ils font un nouveau clone des fichiers à chaque fois, de sorte que tous les fichiers sont neufs selon le système de fichiers.

Après avoir publié cet article, Adam Collier a partagé comment il a résolu ce problème, en éditant le .mdx fichier au moment de la validation avec lint-stage. Je l’ai implémenté dans mon propre blog et je peux confirmer que cela fonctionne très bien. 😄

Avant d’écrire ce post, j’ai demandé sur Twitter s’il y avait quelque chose qui intéressait particulièrement les gens :

Malheureusement, je n’ai aucun moyen de répondre à toutes les questions que j’ai reçues 😅 Certaines des questions étaient suffisamment larges pour que je doive écrire toute une série d’articles pour pouvoir y répondre !

Mais je peux passer en revue certaines des questions les plus courantes.

A l’intérieur de mon src/components dossier, il y a environ 150 composants. Ce sont les composants généraux “à l’échelle de l’application”, des trucs comme Logo et RainbowButton et Boop.

Chaque composant obtient son propre répertoire :


J’aime beaucoup ce motif parce qu’il garde le src/components répertoire relativement clair, tout en me permettant de créer autant de fichiers par composant que nécessaire. La création de cette structure peut être pénible, mais j’utilise un outil de ligne de commande que j’ai créé pour le rendre super rapide et indolore.

j’ai aussi un src/post-helpers dossier. Ici, je stocke tous les composants “uniques” pour des messages spécifiques, des trucs comme le SpringMechanism composant que nous avons vu plus tôt. Idéalement, je colocaliserais ces composants avec leurs articles de blog, mais Next est assez strict sur ce qu’il permet dans le pages répertoire (et next-mdx-enhanced vous oblige à y conserver vos postes).

Je n’en ai pas vraiment 😅 Comme il s’agit d’un site statique, il n’y a pas beaucoup de “flux critiques”.

J’utilise Cypress sur ma plate-forme de cours, cependant, et j’en suis plutôt satisfait !

Lien vers cette rubrique

Comment trouver des idées d’articles ?

Il se trouve que j’ai écrit pas mal de choses à ce sujet dans ma newsletter récemment ! Vous pouvez lire le problème dans l’archive.

Heh, donc, j’avais l’habitude d’utiliser le SDK Twitter standard. J’ai trouvé que cela ralentissait tout mon site, cependant.

J’ai créé un composant, FakeTweet. C’est ce que j’utilise pour ça :

Il s’agit d’une solution très low-tech. Je code en dur toutes les données. Voici à quoi cela ressemble dans le MDX :


Le problème avec cette approche est que les données deviendront obsolètes. Les gens changent leurs noms d’affichage et leurs avatars, mais mon site ne suit pas le rythme. À un moment donné, j’écrirai un assistant de construction qui récupérera les données de l’API Twitter à l’aide des identifiants de tweet.

Pour des raisons que j’ai partagées précédemment, ce blog est à code source fermé, donc malheureusement, il n’y a pas de lien vers un référentiel Github pour que vous approfondissiez. Cela dit, j’ai activé les sourcesmaps, vous pouvez donc parcourir le code frontal de votre navigateur !

Je vous encourage cependant à ne pas trop vous concentrer sur un aspect particulier de ce blog. Créer vos propres éléments personnalisés est l’une des meilleures parties de la création de votre propre blog ! Votre blog est votre laboratoire personnel et votre terrain de jeu : expérimentez différentes idées et voyez ce que vous pouvez proposer 😄 vous vous amuserez beaucoup plus et créerez quelque chose de bien plus mémorable et captivant.