Articles
Utiliser du front-end pur pour créer un blog multilingue « dynamique + article »
J'ai transformé mon site Web en un blog multilingue "dynamique + article" : le contenu est géré avec Markdown, la traduction est terminée lors de la publication, les pages statiques sont générées lors de la construction et le navigateur se charge de la lecture et du changement de langue. Cet article détaille le modèle de contenu, les pipelines partagés, les compromis entre le chargement à la demande et le référencement, et ce que sauve exactement le front-end pur.
· 10 min de lecture
J'espère que mon site Web pourra être utilisé pour noter un paragraphe de réflexions ou pour rédiger clairement un problème technique complet. Mettez « dynamique » dans le court et « article » dans le long. Les deux peuvent être liés l'un à l'autre, et lorsque le visiteur change de langue, le contenu doit également être commuté ensemble.
Cette exigence semble très ordinaire, mais si vous ajoutez dès le début une base de données, un backend et une API de contenu, puis que vous gérez la version de chaque langue, cela deviendra bientôt un autre produit nécessitant une maintenance à long terme.
J'ai choisi de limiter d'abord les exigences à ce que je peux réellement utiliser : **Un auteur, saisit le contenu dans Git et reconstruit le site Web après la publication. Le lecteur n'a besoin que de fichiers statiques et d'un navigateur. ** Il s'agit de la structure actuelle "dynamique + article" de stephen.taipei.
Le front-end pur fait référence à la fin de la lecture#
Commençons par clarifier les limites. Ce projet utilise React, Vite et Tailwind. Node.js est toujours requis pour le traitement du contenu, la conversion d'images et la création de scripts ; la traduction fera également appel à des services externes lors de la phase de publication. Après le déploiement, les fichiers statiques sont fournis par Nginx.
Donc, ce que j’entends par front-end pur, c’est que lorsque les lecteurs ouvrent le site Web, ils n’ont pas besoin d’un back-end de blog spécial pour rechercher des informations, regrouper des pages ou traduire en temps réel. Il dispose d'un processus de construction et d'un hôte, mais pas d'API d'application persistante ni de base de données pour la lecture des articles.
Si le travail est fait à l’avance, la fin de lecture sera beaucoup plus facile.
Les actualités et articles partagent la même source, chacun a son propre modèle#
Le contenu est concentré dans content/, et le texte original est entièrement Markdown. Les deux contenus ne sont pas regroupés dans le même format :
content/
tags.yaml
tags.locales.yaml
notes/2026/20261004-0944.md
notes/2026/20261004-0944.locales.yaml
articles/frontend-only-multilingual-blog/
index.md
locales.yaml
cover.webp
Les dynamiques sont des cartes courtes. Le nom du fichier utilise la minute de l'heure de Taipei comme identifiant. Cela peut être marqué par des idées, de l'apprentissage, en ligne, de la triche ou de la lecture. Il peut être établi en écrivant simplement un paragraphe sans avoir à penser à un titre à chaque fois. Un rappel apparaîtra s’il dépasse 500 mots, et il devra être transformé en article s’il dépasse 1 000 mots.
Les articles ont un slug, un titre, un résumé, une date fixes et peuvent également avoir une couverture et une heure de mise à jour. L'URL ne sera pas modifiée une fois rendue publique afin d'éviter que le lien partagé d'origine ne devienne invalide.
Les deux sont liés en utilisant article:. Pour les mises à jour, vous pouvez d'abord écrire la question, puis créer un lien vers l'article complet ; la page de l'article répertoriera les mises à jour qui y font référence à l'envers. Le contenu court conserve le point de départ actuel et le contenu long est chargé de terminer la dérivation. Il n’est pas nécessaire de publier le même texte intégral des deux côtés.
Laisser les règles de contenu bloquer les erreurs avant de créer#
Si Markdown était simplement ajouté à l'image, de nombreux problèmes ne seraient constatés qu'après sa mise en ligne : des dates erronées, des articles sans lien, des images sans alts ou le même sujet écrit avec trois balises similaires.
J'ai mis ces règles dans le pipeline de contenu partagé. loadContent Lisez le fichier, vérifiez les champs avec Zod, puis traitez les relations Markdown, images, langue et contenu. L'inspection CLI et la construction de Vite utilisent ce pipeline pour réduire l'écart entre « la machine locale dit que cela fonctionne, mais pas l'environnement officiel ».
La balise n'accepte que tags.yaml clés enregistrées ; des articles référencés dynamiquement et d'autres dynamiques doivent exister ; L'aperçu local peut lire les brouillons et la version officielle exclura les brouillons. Ces règles ne reposent pas sur la mémorisation lors de la publication, mais bloquent en réalité les inspections.
Markdown a aussi des limites de sortie bien définies : le raw HTML n’est pas émis directement, le HTML généré est assaini et les blocs de code sont colorés lors du build. Les images de contenu référencées par des chemins relatifs locaux sont converties en WebP, avec une largeur maximale de 1600px, et leurs métadonnées EXIF, y compris les données GPS, sont supprimées. Les articles, les cartes et les aperçus de partage utilisent les images traitées. Les images distantes et celles référencées par des chemins absolus depuis la racine ne passent pas par cette conversion locale.
Le coût du système multilingue sera payé lors de la publication de l'article.#
Le site Web prend actuellement en charge 22 langues. Le texte original en chinois traditionnel est écrit une seule fois et les titres, résumés, descriptions de textes et d'images des 21 autres langues sont stockés dans le locales.yaml à côté.
Voici une décision qui me tient à cœur : **Les lecteurs n'ont pas à attendre la traduction. ** content:localize Générez des traductions lors de la publication. La version officielle lit uniquement les fichiers existants et n'appelle pas à nouveau le service de traduction à chaque déploiement.
Utilisez le résumé des sources pour comparer le texte original et le texte traduit. Si le texte original est modifié, l'ancienne traduction ne peut plus être considérée comme la dernière version ; si une famille de langues manque ou si la traduction est obsolète, content:check échouera. Le processus de traduction protège le code, le code en ligne et les cibles de lien de la traduction des identifiants techniques ou des URL.
Cela ne signifie pas que la traduction automatique ait la qualité d’une édition humaine. Il aborde l’exhaustivité et la cohérence actualisée, et le langage et le ton professionnels nécessitent encore une relecture. Le texte original qui n'a pas été modifié conservera la traduction existante et le texte modifié manuellement ne sera pas écrasé lors d'une nouvelle exécution.
Lisez le résumé dans la liste, cliquez pour charger l'article#
Une fois le contenu augmenté, si la page d'accueil charge le texte intégral de chaque article et de chaque famille de langues, le front-end pur sera très lent.
Le plugin Vite répartit le contenu entre trois points d’entrée : virtual:content/latest pour la page d’accueil, virtual:content/index pour les pages de contenu et un virtual:content/article/<slug> distinct pour chaque article. La page d’accueil affiche les trois notes les plus récentes et trois cartes d’articles. Les données d’une note comprennent toujours le texte intégral et ses traductions ; la carte n’en affiche qu’une version tronquée. Les cartes des articles longs ne contiennent que le résumé et les données associées ; le corps complet est chargé séparément pour chaque article avec dynamic import.
Suspense de React est responsable de l'attente du texte et la limite d'erreur externe est responsable de l'affichage du message d'échec de chargement. Lorsque la demande de texte échoue, le titre, le résumé et la navigation dans les pages restent.
Il y a aussi des compromis à faire ici : actuellement, il y a un morceau de texte par article, qui contient des traductions de chaque famille de langues, et il n'a pas encore été divisé en un seul morceau par famille de langues. Le texte intégral dynamique et les traductions sont saisis dans l'index du contenu. Alors que le volume de contenu continue d'augmenter, la taille de l'index et le nombre de téléchargements de texte multilingue sont les prochaines étapes à mesurer.
Il doit y avoir une commutation frontale et une entrée pour la lecture statique.#
Nous ne faisons que du SPA, et lorsque nous le partageons, nous ne voyons souvent que le titre et l'image sur la page d'accueil. Cela ne suffit pas pour l'article.
Par conséquent, lors de la configuration, chaque URL dynamique, d'article et de balise a son propre shell HTML, qui contient le titre, la description, le canonique, l'Open Graph et le JSON-LD, et le texte original lisible est placé dans noscript. Vous pouvez obtenir le contenu et la couverture exclusive de cet article sans exécuter JavaScript.
Le même élément de contenu produira également feed.xml, sitemap-content.xml, la version Markdown de l'article et l'index de l'article pour llms.txt. Il s'agit de produits intégrés, et non d'un autre ensemble de contenu nécessitant une synchronisation manuelle.
Cependant, les limites du référencement multilingue doivent également être claires : **Actuellement, les mises à jour et les articles partagent la même URL dans chaque langue, et la langue est changée par le front-end ; les pages de contenu statique, les flux et les versions Markdown conservent le texte original. ** La page d'accueil dispose également d'un pré-rendu pour la langue et les modes clair et sombre. Cela ne signifie pas que chaque article comporte également 22 pages linguistiques indexables indépendamment.
Si vous devez autoriser chaque famille de langues à être incluse par les moteurs de recherche à l'avenir, vous devrez ajouter le routage des familles de langues, les pages statiques correspondantes et le hreflang. C'est une autre exigence à laquelle cet ensemble ne répond pas encore.
La publication est aussi un processus de livraison#
Mon processus de publication est le suivant : écrire le texte original, générer la traduction, vérifier le contenu, le soumettre à Git, puis le fusionner dans main. Les actions GitHub existantes reconstruiront le site Web et le déploieront.
npm run content:localize
npm run content:check
npm run test:content
npm run build
Si toutes les langues ne sont pas encore couvertes, je complète d’abord les traductions manquantes. Les articles en brouillon restent dans l’aperçu local. La sortie de production vérifie aussi l’absence de pages de brouillon et recherche leurs marqueurs dans les principaux fichiers publics, comme le feed, le sitemap, la page d’accueil et les bundles JavaScript. Comme le code, le contenu a des diffs et un historique de versions, et peut être restauré en même temps.
Cette méthode sacrifie les mises à jour en temps réel : si vous modifiez un morceau de texte, vous devez attendre un déploiement. Mais pour mon site Web technique, je suis prêt à échanger cette attente en échange d'un backend de moins, d'une dépendance de période d'exécution de moins et d'un enregistrement de modification plus facile à suivre.
Quand est-il approprié de faire cela ?#
Si vous êtes auteur, que la fréquence de publication est reconstituable, que le contenu est principalement du texte, et que vous êtes habitué à Git, cette architecture est très adaptée. De courtes mises à jour abaissent le seuil d'écriture, l'article conserve une dérivation complète et le multilingue permet au même contenu d'être lu par plus de personnes.
Si les exigences concernent la collaboration en temps réel entre plusieurs personnes, la gestion des autorisations, les commentaires des lecteurs, la connexion ou l'abonnement, alors le CMS et le backend doivent être réévalués. Le front-end pur ne fera pas disparaître automatiquement ces besoins, mais je ne les ai pas mis dans le cadre de ce blog pour le moment.
Les principes de conception cette fois sont très proches de ce que j'ai écrit dans Modèle d’architecture pour plusieurs mises en page et plusieurs thèmes : d'abord séparer clairement les responsabilités, puis laisser les règles partagées maintenir la cohérence.
Pour ce site, l'essentiel est de me permettre de continuer à écrire et de savoir quel contenu est réellement diffusé à chaque fois que je publie.