À propos
Développement web

Ouverture de mon blog !

May 18, 2021 — 16 minutes de lecture
vues

Que ce soit du choix de la plateforme, à la façon dont je gère mes données avec DatoCMS, ou pour parler des diverses difficultés que j'ai pu rencontrer. Je reviens, dans cet article, sur les détails de l'ouverture de mon blog, antoinelin.com.

Introduction

Après plus de 3 ans de "site en construction", mon blog est enfin en ligne. Créer son site internet, c'est bâtir un espace qui est censé refléter votre personnalité. Un petit coin d'Internet qui vous appartient sur lequel vous voulez vous exprimer. Que ce soit par le contenu, mais aussi par la direction artistique, ou l'attention portée aux détails, c'est votre image en ligne que vous défendez. Subtilement, mais de façon discernable, chaque décision que vous prenez et chaque élément que vous montrez à son importance. Et si vous êtes indépendant, c'est également une vitrine marketing que vous construisez. Le site où vous lisez ces quelques lignes représente, pour moi, l'aboutissement d'un profond travail d'introspection. De la première maquette, au choix de quelle librairie de CSS in JS j'ai utilisé, ou pour parler des difficultés que j'ai rencontré. J'aimerais revenir, dans cet article, sur le pourquoi et sur le comment j'ai conçu et développé mon site internet antoinelin.com.

Solution clé en main, ou from scratch ?

Si vous avez déjà envisagé de créer un blog, et surtout si vous êtes un développeur, vous vous êtes peut-être déjà demandé, comme moi, quelle est la solution à plébisciter en matière de plateforme de publication en ligne. Développer un site, from scratch, indépendant ? Passer par un thème WordPress ? Ou utiliser un service tel que Medium pour ne pas "réinventer la roue" et bénéficier de son écosystème de partage ? Au final, le temps qu'on met à concevoir et développer son site internet, c'est le temps qu'on peut mettre à créer du contenu, n'est-ce pas ?

Personnellement, je voulais que ma plateforme soit une expression de ma personnalité. On éjecte donc d'emblée la piste du thème WordPress qui représente un frein à la créativité. D'autant plus que je préfère avoir la main sur la technologie pour m'assurer du niveau de qualité du code.

Concernant les services comme Medium, j'estime qu'ils représentent l'équivalent des fast-foods du contenu rédactionnel en ligne. Je ne souhaite pas que mes articles se perdent parmis les millions d'autres sur ces plateformes.

Bonjour Monsieur Lin. Voici les cinq articles du jour. Vous avez terminé de les lires ? Vous en reprendrez bien un de plus pour le dessert. Celui-ci ne vous plaît pas ? Aucun problème, vous pouvez swipper au prochain d'un mouvement de doigt.

Enfin, créer son site internet, c'est se soucier des composantes fondamentales d'un projet web "classique". UX, UI, travail de SEO, contenu, et pour les plus tech, c'est un moyen de montrer son code en dehors des dépôts Git. Dans mon cas, c'est aussi une vitrine pour mes clients. Par ce biais, je leur prouve que je suis capable de développer un produit, rien de plus utile pour prouver que je suis légitime de construire le leur.

Si je devais résumer, c'est surtout par critère d'indépendance que j'ai choisi de ne pas passer par un thème ou un service. Beaucoup de développeurs pensent que prendre le temps de créer son propre site est un travail supplémentaire inutile. Ce qu'ils ne comprennent pas, c'est que par ce biais, ils ont la possibilité de se démarquer des autres profils, qui eux ne le font pas.

L'inconvénient, c'est que j'ai mis bien plusieurs mois à le terminer. En cause, mon envie de vouloir (trop) bien faire, qui m'a valut beaucoup d'itérations sur la partie graphique et beaucoup d'hésitations sur le choix des technologies.

Alors, blog from scratch, thème pré-fait, ou service dédié ? Vous avez mon avis sur la question.

Un blog ? Pour y proposer quel contenu ?

C'est bien beau de développer un blog, mais ce qui en fait sa pertinence, c'est avant tout son contenu vous ne pensez pas ? Voici une courte liste des différents articles que vous pourrez trouver ici :

  • Du contenu éditorial sur l'univers du développement web,

  • Des tutoriels, sous la forme de solutions étapes par étapes à des problématiques précises,

  • Des nouvelles de mon activité professionnelle,

  • Des retours sur mes expériences et sur les difficultés que j'ai pu rencontrer dans ma jeune carrière de développeur web,

  • Y écrire, par moments, des billets d'humeurs sur des sujets qui peuvent me toucher.

Dans la généralité, j'aimerais que ce blog soit orienté autour du monde du développement web et de la technologie. Mais je souhaite également que cette plateforme soit un endroit d'expression personnel, où je me sens libre de publier ce que bon me semble.

Également (et attention, interlude publicitaire) vous trouverez sur ma chaîne YouTube des vidéos en rapport avec certains de mes articles. Le plus souvent, vous aurez le droit à la vidéo en fin de page ! Donc n'hésitez pas à vous abonner 👀

Image d'illustration par Christopher Gower - Unsplash

Et niveau tech, ça donne quoi ?

Rapide coup d'oeil sur la stack


Front-end

Le site a été construit sous TypeScript et React.js avec le framework Next.js.
Par défaut, Next.js effectue un "pré-rendu" de chaque page : il génère le contenu à l'avance via deux solutions différentes, au choix. La première est la génération statique, qui récupère la donnée et transmet le contenu lors de la construction (au build) du site. Et la seconde est le rendu côté serveur, qui pré-rend, côté serveur donc, une page à la demande de utilisateur qui la visite (au runtime). Pour ce blog, j'ai opté pour la génération statique afin de limiter les pics de charge serveur et optimiser les performances de rendu. Si vous souhaitez en savoir plus sur ce concept, je vous invite à lire cette page de la documentation officielle de Next.js.

Pour le style, j'utilise TailwindCSS en mode preview avec l'option Just-in-time activé. Pour le moment, j'écris mon style en inline (directement rédigé dans le DOM virtuel de React) mais qui devrait passer par du CSS Modules d'ici peu. Et pour composer mes classNames j'utilise l'utilitaire éponyme : classNames.

Enfin, les animations sont faites avec Framer Motion pour la manipulation du DOM et avec Paper.js pour le curseur "iPad OS".

Back-end

Pour le back-end, Next.js nous met à disposition des routes API accessibles sous REST dans lesquels on peut déclarer du code qui s'exécutera côté serveur. Très utile si vous souhaitez faire de la persistence avec une base de donnée ou faire appel à des services tiers en évitant de compiler vos tokens dans le code front-end. En parlant de services tiers et pour la gestion du contenu du site, j'utilise un Headless CMS qui s'appelle DatoCMS et je récupère la donnée en GraphQL avec la librairie Urql. L'ensemble de ma logique est écrite sous observables avec RxJS et j'utilise les API suivantes pour afficher les différents petits modules sur le site :

  • Spotify API pour le composant dans le footer qui affiche la musique que j'écoute en direct sur Spotify,
  • Twitch API pour afficher la bannière qui avertit quand je suis en direct sur Twitch,
  • Google Analytics pour récupérer et afficher le nombre de vues de chacun de mes articles.

Pour réduire le nombre d'appels aux API ci-dessus et réduire le temps de mes requêtes entre mon front-end et mon back-end, j'ai ajouté de la persistence avec un cache normalisé lru-cache.

Hébergement

Ce blog est déployé avec Vercel. Ayant essayé la première version quand le service s'appelait encore Now, je dois dire que le produit a beaucoup changé depuis. Super simple à mettre en place et plutôt bien optimisé avec Next.js, chaque fois que je modifie du code et que je le push sur Github, le site est déployé automatiquement et mis à jour en production. Moi qui suis friand des solutions d'orchestration à la Kubernetes et des CI/CD automatisé, je cherchais une solution qui soit moins overkill pour un blog, je suis très satisfait par Vercel.

Services tiers

Pour analyser les comportements des utilisateurs, je m'aide des services tiers suivants :

Gestion, flux et affichage des données

J'utilise un Headless CMS pour gérer le contenu de mon blog : DatoCMS.

Vous voyez WordPress ? DatoCMS, c'est le même principe, à la différence que je n'ai pas besoin de l'héberger et que je n'ai qu'une interface d'administration avec laquelle j'ajoute mon contenu que je récupère par requêtes API. Ce service à l'immense avantage de me laisser la liberté d'utiliser importe quelle technologie pour le front-end.

Avec DatoCMS, je gère :

  • Mes articles,

  • La structure et le contenu de chaque page,

  • Les meta, globales au site, et par page.

J'ai même une connexion via webhook avec Vercel pour déployer mon site.

De la même manière que WordPress, DatoCMS me permet de créer mes différents modèles de données. Voici ceux que j'utilise pour le blog :

Les modèles de données que j'utilise pour mon blog
  • all_posts
    , où je définis la structure de ma page
    /posts
    ,
  • category
    , où je définis l'ensemble de mes catégories d'articles, avec le titre et la couleur,
  • home
    , où je définis la structure de ma page d'accueil
    /
    ,
  • legal
    , même chose pour la page de mentions légales
    /legal
    ,
  • navigation
    , pour éditer ma situation géographique et ma première disponibilité,
  • post
    , modèle de donnée pour mes articles,
  • question
    , pour les questions FAQ que j'ajoute en fin d'article,
  • single_post
    , pour la structure de ma page
    /posts/[post_slug]
    , là où je mets le contenu qui doit être le même pour chaque article (les articles les plus populaires, l'article en avant, etc.).

À l'image d'un ACF pour WordPress, je peux définir les différents champs de mon modèle de données. Voici l'ensemble des types de champs que DatoCMS nous fournit :

Les différents champs possibles avec DatoCMS

Je n'ai plus qu'à créer ou modifier du contenu :

Ajouter, modifier, ou supprimer un article
Édition d'un article

Derrière, je n'ai plus qu'à préparer ma requête GraphQL pour récupérer cet article :

# SinglePost.query.graphql

query SinglePost($locale: SiteLocale!, $slug: String!) {
site: _site {
favicon: faviconMetaTags {
attributes
content
tag
}
}
post(locale: $locale, filter: {slug: {eq: $slug}}) {
id
slug
_firstPublishedAt
seo: _seoMetaTags {
tag
attributes
content
}
_allSlugLocales {
value
locale
}
displayPinnedPost
displayNetworks
title
introduction
[...]
}
}
GraphQL

À tout moment, je peux explorer les options de ma requête en utilisant l'éditeur GraphiQL intégré à DatoCMS (l'onglet API Explorer dans la barre de navigation)

API Explorer de DatoCMS

Il me suffit, ensuite, de récupérer ce contenu et de l'afficher dans mon front-end.

Travaillant sous TypeScript, j'utilise une commande

codegen
qui me lance une introspection de mon serveur GraphQL distant (soit mon endpoint GraphQL DatoCMS) pour récupérer le schéma et le transformer en typage TypeScript via GraphQL Code Generator.
Si vous êtes intéressé par le sujet, je ferai un tutoriel complet dédié au fonctionnement d'un Headless CMS avec GraphQL et TypeScript.

Comme expliqué plus haut, j'ai choisi la solution SSG (génération statique du contenu) avec Next.js qui me permet de récupérer mon contenu à la construction du site. Il me faut alors utiliser une fonction

getStaticProps
dans la page qui m'intéresse qui s'occupera d'initialiser mon client GraphQL (Urql) et d'exécuter la requête précédente :

// pages/posts/[post_slug].tsx

export const getStaticProps: GetStaticProps = async ({ params, preview = false, locale = 'fr' }) => {
const ssrCache = ssrExchange({ isClient: false })
const client = initGraphQLClient({ preview, ssrCache })
const slug = params?.post_slug

const { data, error } = await client
.query<SinglePostQuery>(SinglePostDocument, {
locale,
slug,
})
.toPromise()

if (error || !data || !data.post) {
return {
notFound: true,
}
}

return {
props: {
urqlState: ssrCache.extractData(),
data: data ?? null,
preview,
previewTitle: data?.post?.title ?? 'Single Post',
},
revalidate: 1,
}
}
TypeScript

Puis de définir les URL qui nécessitent d'être procédées et générées statiquement par Next.js en utilisant

getStaticPath
:

// pages/posts/[post_slug].tsx

export const getStaticPaths: GetStaticPaths = async () => {
const client = initGraphQLClient({})

const { data: frData } = await client
.query<AllPostQuery>(AllPostDocument, {
locale: 'fr',
})
.toPromise()

const { data: enData } = await client
.query<AllPostQuery>(AllPostDocument, {
locale: 'en',
})
.toPromise()

const formatPath = (post: { slug: string }) => ({
params: {
post_slug: post.slug,
},
})

const frPaths = (frData?.allPosts as { slug: string }[]).map(formatPath) ?? []
const enPaths = (enData?.allPosts as { slug: string }[]).map(formatPath) ?? []

return {
paths: [...frPaths, ...enPaths],
fallback: true,
}
}
TypeScript

Next.js récupère déjà la route courante

/posts/[post_slug].tsx
, ce qu'il a besoin, c'est qu'on lui communique la liste des slug qui correspondent aux articles qui doivent être générés. Pour éviter d'avoir à relancer la construction complète du site à chaque fois qu'on ajoute un article, il suffit d'ajouter
fallback: true
dans la réponse, ce qui fera en sorte de lancer une génération statique pour cette page uniquement à la première visite d'un utilisateur.

Astuce : les robots d'indexations (comme celui de Google) déclenchent les fallback quand une page n'a pas été générée à la construction du site. Si vous avez des soucis d'indexation de vos pages (mauvaise URL, balises canonical étrange, etc.) vérifiez que l'erreur ne vient pas de là.

Vous savez, maintenant, comment je gère et récupère la donnée de mon blog. Derrière, j'apporte du traitement pour le contenu textuel "riche" uniquement où j'utilise Remark pour transformer ce que je reçois en Markdown de DatoCMS vers de l'HTML.

Astuce : on en reparlera dans un article dédié, mais si vous souhaitez injecter de l'HTML dans votre contenu Markdown qui doit être utilisé comme tel, veillez à bien "nettoyer" vos chaînes de caractères pour vous protéger d'attaques XSS ou autre. Je vous conseille isomorphic-dompurify avec Next.js, qui est une variante

ìsomorphique
(qui marche côté serveur et client) de DOMPurify.

Quelques détails en vrac

Thèmes de couleurs et réduction des animations visuelles

Le site se base sur les caractéristiques média

prefers-color-scheme
et
prefers-reduced-motion
pour afficher le thème de couleur par défaut et désactiver, ou non, le curseur iPad OS ainsi que les animations susceptibles de provoquer un mal des transports (motion sickness).

Pour le choix du thème de couleurs :

useEffect(() => {
const getColorScheme = (event: MediaQueryListEvent) => {
const newColorScheme = event.matches ? 'dark' : 'light'

setActiveTheme(newColorScheme)
}

if (isBrowser && window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', getColorScheme)
}

return () => {
if (window.matchMedia) {
window
.matchMedia('(prefers-color-scheme: dark)')
.removeEventListener('change', getColorScheme)
}
}
}, [])
TypeScript

Quelques points à noter :

  • J'attribue une classe CSS au

    body
    en fonction du choix du thème,

  • Je stocke le choix de l'utilisateur en

    localStorage
    pour faire de la persistence,

  • Je récupère la préférence du navigateur au

    useEffect
    pour assurer la présence de l'objet
    window
    ,

  • Je gère la préférence du thème de couleur dans un

    context
    React.

Pour la définition des thèmes, j'ai un fichier

theme.css
qui contient les classes que j'attribue au
body
. Dans chaque classe, je définis des variables CSS qui sont utilisés dans le fichier de configuration
tailwind.config.js
.

/* themes.css */

.theme-light {
--color-bg-primary: #ffffff;
...
}

.theme-dark {
--color-bg-primary: #131313;
...
}
CSS
// tailwind.config.js

module.exports = {
theme: {
backgroundColor: {
primary: 'var(--color-bg-primary)',
}
}
...
}
JavaScript

Reste à l'utiliser dans mon DOM React avec TailwindCSS :

<div className="bg-primary">
...
</div>
TypeScript

La gestion des préférences motion est similaire :

useEffect(() => {
const getMotionPreference = (event: MediaQueryListEvent) => {
const newMotionPreference: ReduceMotionPreference = event.matches ? 'no-preference' : 'reduce'

setPrefersReduceMotion(newMotionPreference)
}

if (isBrowser && window.matchMedia) {
window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', getMotionPreference)
}

return () => {
if (window.matchMedia) {
window
.matchMedia('(prefers-reduced-motion: reduce)')
.removeEventListener('change', getMotionPreference)
}
}
}, [])
TypeScript

Quelques points à noter :

  • Comme pour la gestion du thème, j'attribue une classe CSS au

    body
    en fonction des préférences de l'utilisateur,

  • Comme pour la gestion du thème, je gère la logique dans un

    context
    React pour récupérer la valeur dans chaque composant facilement,

  • La valeur de la préférence est stockée dans un

    useState
    transmis par le
    context
    , ainsi qu'une méthode pour la modifier.

Il me reste plus qu'à modifier les animations de mouvement (

rotate
,
translate
,
scale
). Un exemple avec le composant qui gère l'animation de mon texte :

import { useContext } from 'react'

export default function SplitText({ text, ...props }: Props) {
const { motionPreference } = useContext(MotionPreferenceContext)
const words = text.split(' ')

return (
<>
{words.map((word, i) => (
<span key={`word-${i}`} className="inline-block overflow-hidden">
<motion.span
variants={{
initial: {
y: motionPreference === 'reduce' ? 0 : '100%',
opacity: motionPreference === 'reduce' ? 0 : 1,
},
show: i => ({
y: 0,
opacity: 1,
transition: {
delay: showDelay + i * 0.01,
stiffness: 500,
damping: 300,
},
}),
hide: i => ({
y: motionPreference === 'reduce' ? 0 : '-105%',
opacity: motionPreference === 'reduce' ? 0 : 1,
transition: {
delay: i * 0.01,
stiffness: 500,
damping: 300,
},
}),
}}
className="inline-block"
style={motionPreference === 'no-preference' ? { willChange: 'transform' } : {}}
custom={i}
>
{word + (i !== words.length - 1 ? '\u00A0' : '')}
</motion.span>
</span>
))}
</>
)
}
TypeScript
Autres détails concernant le site
  • Je n'utilise pas de

    target: _blank
    sur mes ancres pour laisser le choix à l'utilisateur d'ouvrir un lien dans un nouvel onglet avec
    CMD
    ou
    CTRL
    s'il le souhaite,

  • Chaque image du site est optimisée via Imgix grâce à DatoCMS. En savoir plus,

Conclusion

Je n'ai pas couvert tout ce qui a été fait sur ce blog pour ne pas faire un article à rallonge. Encore une fois, tous les sujets que j'estime intéressants comme la gestion de l'internationalisation du site, la connexion avec Spotify ou la création du curseur iPad OS, bénéficieront d'un article dédié. Pour ne rien louper, vous pouvez me suivre sur Twitter, je communiquerai chaque sortie d'article.

J'aimerais continuer d'améliorer ce blog et développer de nouvelles fonctionnalités. Pour le moment, j'ai prévu d'ajouter ces mises à jour :

  • De quoi s'abonner à un flux RSS pour rester au courant des prochaines sorties d'articles,

  • Une solution pour grossir les images des articles en mode "théâtre",

  • Des améliorations concernant l'accessibilité et l'optimisation de la navigation pour les personnes malvoyantes,

  • Une barre de progression pour la lecture des articles, et peut-être un mode "défilement automatique" optionnel,

  • Des petites mises à jour pour simplifier la navigation du site (rendre les catégories des articles cliquables par exemple),

  • Transformer le blog en PWA pour permettre la lecture hors ligne.

J'espère que ce premier article vous aura plus, c'est une discipline nouvelle pour moi et je suis super impatient de pouvoir partager plein d'autres sujets avec vous.

Si vous avez un avis ou un retour, vous pouvez utiliser ce formulaire, et je serais heureux d'en discuter avec vous sur Twitter (Merci par avance pour le temps que vous prendrez 🙏🏻).

Et si cet article vous a aidé dans une moindre mesure, n'hésitez pas à me le faire savoir également !

À plus dans le bus ! 👋🏻

0 vues 
Note : 4.5/5 avec plus de 3 votes.
Émettre un avisPartager sur Twitter

FAQ en rapport avec l'article

  • Qui suis-je ?

    Je m'appelle Antoine Lin, j'ai 25 et je vis près de Paris. J'ai grandi en Seine-et-Marne et j'ai fait mes études à l'école HETIC, Montreuil. Je suis développeur indépendant, et depuis 2015, j'accompagne des marques et des influenceurs en leur livrant des expériences web sur-mesure.

  • Quel contenu je vais trouver sur ce blog ?

    Le type de contenu que vous trouverez sur ce blog concerne en majorité l'univers du développement web et de la technologie (du contenu éditorial, des tutoriels etc.). Mais je souhaite également que cette plateforme soit un endroit d'expression personnel, où je me sens libre de publier ce que bon me semble.

  • Comment le site a été construit ?

    Le site a été construit de zéro en utilisant le language TypeScript et React.js avec le framework Next.js. Pour le style, j'utilise TailwindCSS, et pour les animations Framer Motion. Le site est déployé avec Vercel.

Les dernières publications

Développement web
Utiliser plusieurs API GraphQL avec Urql
02 June 2021 — min de lecture
vues

Si vous êtes familier avec GraphQL, vous avec sûrement déjà eu besoin de récupérer des données sur des services tiers en parallèle de votre API GraphQL principale. Dans cet article, je résume comment j'ai réussi à utiliser plusieurs API GraphQL via un seul et unique client Urql.