Guide pour créer un blog sans serveur avec CMS sur un domaine personnalisé grâce à Hugo + GitHub + Decap CMS + Cloudflare Pages
Ce site, que j’exploitais autrefois sur un domaine personnalisé avec Google Sites (époque Google Apps), est resté longtemps à l’abandon depuis l’arrêt du service et l’URL https://www.ixam.net dormait dans un coin. J’ai fini par décider qu’il était temps de le remettre en service et de lui donner une nouvelle vie : voici le résultat de mes essais avec ChatGPT pour obtenir une plateforme sans serveur qui ne dépende pas d’un service susceptible de disparaître comme Google Sites.
Je pensais honnêtement que ChatGPT suffirait largement pour y arriver sans peine, mais la réalité a été différente : oublis de besoins fondamentaux, débogage en boucle… Il a fallu que je complète par mes propres recherches, notamment en m’appuyant sur le dépôt GitHub https://github.com/patrickgrey/website/, pour finir par construire le site en mode coopération.
La sensation était très forte de diriger un subordonné techniquement compétent mais qui part régulièrement dans la mauvaise direction, et de devoir le remettre sur les rails pour atteindre le résultat voulu. En un sens, c’est impressionnant de voir une IA arriver à ce niveau. Et quand les sujets deviennent vraiment complexes, l’assistant se met vite à m’agacer par son degré d’ineptie insupportable. Est-ce que ce n’est pas finalement très proche du monde réel ?
Pour l’instant, il semble plus efficace de repartir sur un nouveau fil de discussion bien cadré dès que la conversation devient trop chargée, plutôt que d’exiger de l’IA qu’elle comprenne tous les prérequis en accumulant les échanges. Cela dit, en termes de volume de recherche, de travail et d’apprentissage, je n’aurais clairement pas pu mener ce projet seul : l’effet de levier de l’IA générative est incroyable.
Objectifs
- Afficher le blog sur
https://www.example.com/
- Pouvoir se connecter à DecapCMS via
https://www.example.com/admin/
pour créer des articles - Les articles créés sont validés sur GitHub et déployés automatiquement sur Cloudflare
- À ce stade, les coûts d’exploitation supplémentaires sont nuls (hors frais d’enregistrement du domaine)
0. Prérequis et structure des dossiers
0-1. Prérequis
-
Supposons que le domaine (
example.com
) est déjà enregistré -
Le système d’exploitation peut être Windows ou macOS (les exemples de commandes sont fournis pour les deux)
-
Services utilisés (niveau gratuit suffisant)
- Compte GitHub
- Compte Cloudflare (Cloudflare Pages)
- Git (en local)
- Hugo (pour la prévisualisation locale et la compilation sur Cloudflare)
0-2. Structure principale du dépôt (exemple)
hugo.toml
content/
blog/
posts/
data/
functions/
api/
auth.js
callback.js
layouts/
_default/
baseof.html
index.html
list.html
single.html
static/
_headers
admin/
config.yml
index.html
css/
main.css
Cette structure remplit les rôles suivants :
hugo.toml
… Configuration globale du site (baseURL à remplacer par l’URL de production)functions/api/*.js
… Cloudflare Pages Functions (OAuth GitHub pour/api/auth
et/api/callback
)layouts/_default/*.html
… Modèles Hugostatic/admin/
… Interface et configuration de DecapCMSstatic/css/main.css
… Apparence (CSS)static/_headers
… Paramètres HTTP pour Cloudflare Pages (optionnel)
Dans cette procédure, les éléments à adapter à votre environnement sont indiqués explicitement sous la mention « Contrôle de remplacement ».
1. Préparation
1-1. Créer un compte GitHub
- Ouvrir GitHub dans un navigateur et s’inscrire
1-2. Créer un compte Cloudflare
- Ouvrir Cloudflare dans un navigateur et s’inscrire
1-3. Installer Git et Hugo
-
Windows (PowerShell)
PS C:\Users\alice> winget install Git.Git PS C:\Users\alice> winget install Hugo.Hugo.Extended
-
macOS (Terminal)
mac:~ dev$ brew install git mac:~ dev$ brew install hugo
2. Procédure principale (jusqu’au déploiement)
2-1. Préparer le dépôt en local
-
Créer un dépôt vide sur GitHub
- Interface : GitHub > New repository
- Nom suggéré :
my-hugo-blog
-
Cloner le dépôt en local
-
Windows
PS C:\work> git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git PS C:\work> cd .\my-hugo-blog
-
macOS
mac:~/work dev$ git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git mac:~/work dev$ cd my-hugo-blog
-
-
Placer l’ensemble des dossiers et fichiers présentés ci-dessus à la racine de ce dépôt
Contrôle de remplacement (à ajuster impérativement)
-
hugo.toml
baseURL = "https://www.example.com" # ← remplacer par l'URL de production (ex. : https://www.example.com) languageCode = "ja-jp" title = "Example Blog" publishDir = "public" [permalinks] blog = "/blog/:year/:month/:slug/"
-
static/admin/config.yml
backend: name: github repo: <YOUR_GH_USERNAME>/my-hugo-blog # ← remplacer par votre dépôt GitHub branch: master # ← nom de la branche par défaut (main ou master) base_url: https://www.example.com # ← remplacer par l'URL de production auth_endpoint: /api/auth # ← point de terminaison Functions (fixe) media_folder: static/uploads public_folder: /uploads
Remarque :
functions/api/auth.js
etfunctions/api/callback.js
peuvent être utilisés tels quels (ils s’appuient sururl.origin
, il n’y a donc aucun paramètre spécifique à votre environnement à coder en dur).
-
Premier commit et push
-
Windows
PS C:\work\my-hugo-blog> git add -A PS C:\work\my-hugo-blog> git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + CF Pages" PS C:\work\my-hugo-blog> git push -u origin master
-
macOS
mac:~/work/my-hugo-blog dev$ git add -A mac:~/work/my-hugo-blog dev$ git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + CF Pages" mac:~/work/my-hugo-blog dev$ git push -u origin master
-
2-2. Créer une application GitHub OAuth (pour la connexion DecapCMS)
-
Interface : GitHub > Settings > Developer settings > OAuth Apps > New OAuth App
-
Saisir :
- Application name :
[DecapCMS](https://decapcms.org/) for my-hugo-blog
- Homepage URL :
https://www.example.com
- Authorization callback URL :
https://www.example.com/api/callback
← important
- Application name :
-
Après la création, relever :
- Client ID
- Client Secret (générer une valeur et la conserver)
Ces éléments seront enregistrés comme variables d’environnement dans Cloudflare Pages.
2-3. Créer le projet Cloudflare Pages
-
Interface : Cloudflare Dashboard > Pages > Create a project > Connect to Git
-
Connecter GitHub et sélectionner le dépôt
my-hugo-blog
-
Paramétrer la compilation :
-
Framework preset :
None
(ou laisser Cloudflare détecter automatiquement) -
Build command :
hugo
-
Build output directory :
public
-
Variables d’environnement :
HUGO_VERSION
=0.128.0
(par exemple, pour correspondre à la version utilisée en local)GITHUB_CLIENT_ID
= (valeur récupérée à l’étape précédente)GITHUB_CLIENT_SECRET
= (valeur récupérée à l’étape précédente)
-
-
Cliquer sur Save and Deploy et attendre le premier déploiement
- Une URL de prévisualisation
*.pages.dev
est générée en cas de succès
- Une URL de prévisualisation
2-4. Associer le domaine personnalisé www.example.com
-
Interface : Cloudflare > Pages > (projet concerné) > Custom domains > Set up a custom domain
-
Saisir
www.example.com
pour l’ajouter -
Si le domaine n’est pas transféré chez Cloudflare :
- Noter les enregistrements DNS affichés dans Cloudflare (ex. :
CNAME www -> <project>.pages.dev
) - Sur l’interface de votre registrar, configurer ces enregistrements conformément aux instructions (les étapes varient selon le registrar)
- Noter les enregistrements DNS affichés dans Cloudflare (ex. :
-
Après propagation, vérifier que
https://www.example.com/
s’ouvre correctement
Astuce : si le domaine est déjà ajouté côté Cloudflare, un simple bouton suffit pour appliquer automatiquement les enregistrements DNS nécessaires.
2-5. Accéder au panneau d’administration DecapCMS
- Ouvrir
https://www.example.com/admin/
dans le navigateur - Cliquer sur le bouton « Login with GitHub » et autoriser GitHub OAuth
- Lors de la première connexion, GitHub demande « Autorisez-vous cette application ? » → cliquer sur Authorize
En cas d’échec de connexion, vérifier à nouveau l’URL de rappel OAuth et les variables d’environnement Cloudflare (
GITHUB_CLIENT_ID/SECRET
).
3. Exploitation (création d’articles, synchronisation locale, modification des templates)
3-1. Créer un article depuis le CMS
- Interface :
https://www.example.com/admin/
- Barre latérale : Blog → New blog
- Saisir :
Title
,Publish Date
,Description
,Body
- Cliquer sur Publish → un commit est généré sur GitHub et Cloudflare déploie automatiquement
Le Markdown généré est placé dans
content/blog/
.
3-2. Mettre à jour le dépôt local après une publication via le CMS
-
Windows
PS C:\work\my-hugo-blog> git pull origin master
-
macOS
mac:~/work/my-hugo-blog dev$ git pull origin master
Cela permet de récupérer en local les derniers fichiers d’article et de continuer sereinement les modifications de templates ou de CSS.
3-3. Ajuster le design et les templates en local
-
Lancer le serveur de développement local
-
Windows
PS C:\work\my-hugo-blog> hugo server -D
-
macOS
mac:~/work/my-hugo-blog dev$ hugo server -D
-
Ouvrir
http://localhost:1313/
dans le navigateur pour vérifier
-
-
Points de modification possibles :
layouts/_default/baseof.html
…<head>
et en-têtes/pieds de pagelayouts/_default/index.html
… Liste « derniers articles » en page d’accueillayouts/_default/single.html
… Corps d’un articlestatic/css/main.css
… Couleurs, police, espacements
-
Valider les modifications et pousser
-
Windows
PS C:\work\my-hugo-blog> git add -A PS C:\work\my-hugo-blog> git commit -m "Update theme/layouts" PS C:\work\my-hugo-blog> git push
-
macOS
mac:~/work/my-hugo-blog dev$ git add -A mac:~/work/my-hugo-blog dev$ git commit -m "Update theme/layouts" mac:~/work/my-hugo-blog dev$ git push
-
Quelques secondes à quelques minutes plus tard, Cloudflare déploie automatiquement
-
3-4. Conseils pour la gestion des branches
- Harmoniser la branche par défaut (
main
oumaster
) entre leconfig.yml
de DecapCMS et la configuration Cloudflare - Pour prévisualiser, créer une Pull Request GitHub : Cloudflare Pages produit alors automatiquement un environnement de prévisualisation
4. Compléments utiles (optionnel)
4-1. Exemple de static/_headers
(ne pas mettre en cache l’interface d’administration)
/admin/*
Cache-Control: no-store
4-2. Robots et sitemap (selon les besoins)
- Préparer
static/robots.txt
pour contrôler l’accès des robots - Ajouter des sorties supplémentaires (RSS, sitemap, etc.) via
outputs
danshugo.toml
5. Problèmes fréquents et solutions
- La connexion CMS tourne en boucle
→ Vérifier que l’URL de rappel GitHub OAuth esthttps://www.example.com/api/callback
et que les variables CloudflareGITHUB_CLIENT_ID/SECRET
sont correctes - La page d’accueil fonctionne mais les articles renvoient 404
→ S’assurer quebaseURL
danshugo.toml
correspond bien au domaine réel. Contrôler dans les journaux Cloudflare quepublic/
est généré - /admin reste vide
→ Vérifier que le chargement de DecapCMS dansstatic/admin/index.html
n’est pas bloqué (ex. par une extension navigateur). Désactiver les extensions et recharger
6. Récapitulatif des éléments à personnaliser
Fichier/paramètre | Clé | Valeur à définir (exemple) |
---|---|---|
hugo.toml |
baseURL |
https://www.example.com |
static/admin/config.yml |
repo |
<YOUR_GH_USERNAME>/my-hugo-blog |
static/admin/config.yml |
branch |
main ou master selon votre configuration |
static/admin/config.yml |
base_url |
https://www.example.com |
Cloudflare Pages | HUGO_VERSION |
Ex. : 0.128.0 |
Cloudflare Pages | GITHUB_CLIENT_ID |
Client ID de l’application GitHub OAuth |
Cloudflare Pages | GITHUB_CLIENT_SECRET |
Client Secret de l’application GitHub OAuth |
Application GitHub OAuth | Callback URL | https://www.example.com/api/callback |
Cloudflare Pages | Custom domain | Ajouter www.example.com |
7. Annexe : exemples de fichiers (à adapter selon vos besoins)
7-1. functions/api/auth.js
// Cloudflare Pages Functions (/api/auth)
export async function onRequest(context) {
const { request, env } = context;
const client_id = env.GITHUB_CLIENT_ID;
try {
const url = new URL(request.url);
const redirectUrl = new URL('https://github.com/login/oauth/authorize');
redirectUrl.searchParams.set('client_id', client_id);
redirectUrl.searchParams.set('redirect_uri', `${url.origin}/api/callback`);
redirectUrl.searchParams.set('scope', 'repo user');
redirectUrl.searchParams.set('state', crypto.getRandomValues(new Uint8Array(12)).join(''));
return Response.redirect(redirectUrl.href, 302);
} catch (err) {
return new Response(String(err?.message || err), { status: 500 });
}
}
7-2. functions/api/callback.js
function renderBody(status, content) {
const html = `
<script>
const receiveMessage = (message) => {
window.opener.postMessage(
'authorization:github:${status}:${JSON.stringify(content)}',
message.origin
);
window.removeEventListener("message", receiveMessage, false);
}
window.addEventListener("message", receiveMessage, false);
window.opener.postMessage("authorizing:github", "*");
</script>`;
return new Blob([html]);
}
export async function onRequest(context) {
const { request, env } = context;
const client_id = env.GITHUB_CLIENT_ID;
const client_secret = env.GITHUB_CLIENT_SECRET;
try {
const url = new URL(request.url);
const code = url.searchParams.get('code');
const response = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: { 'content-type': 'application/json', 'user-agent': 'cf-pages-oauth', 'accept': 'application/json' },
body: JSON.stringify({ client_id, client_secret, code }),
});
const result = await response.json();
if (result.error) {
return new Response(renderBody('error', result), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 401 });
}
const token = result.access_token;
const provider = 'github';
return new Response(renderBody('success', { token, provider }), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 200 });
} catch (error) {
return new Response(error.message, { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 500 });
}
}
7-3. static/admin/config.yml
backend:
name: github
repo: <YOUR_GH_USERNAME>/my-hugo-blog
branch: main
base_url: https://www.example.com
auth_endpoint: /api/auth
media_folder: static/uploads
public_folder: /uploads
collections:
- name: 'blog'
label: 'Blog'
folder: 'content/blog'
create: true
slug: '{{slug}}'
editor:
preview: false
fields:
- { label: 'Title', name: 'title', widget: 'string' }
- { label: 'Publish Date', name: 'date', widget: 'datetime' }
- { label: 'Description', name: 'description', widget: 'string' }
- { label: 'Body', name: 'body', widget: 'markdown' }
9. Points clés pour optimiser SEO et performances
Après la mise en place du blog, les actions suivantes peuvent améliorer encore le référencement et la vitesse d’affichage.
-
Configurer sitemap et robots.txt
- Enregistrer
public/sitemap.xml
(généré par Hugo) dans Google Search Console - Ajouter des règles de crawl dans
static/robots.txt
- Enregistrer
-
Configurer OGP (Open Graph) et les Twitter Cards
- Ajouter
<meta property="og:title">
,<meta property="og:image">
, etc. danslayouts/_default/baseof.html
- Soigner l’apparence lors du partage sur les réseaux sociaux
- Ajouter
-
Convertir les images en WebP
- Exploiter
resources/_gen/images/
et le pipeline Hugo pour générer automatiquement des WebP - Améliorer les temps de chargement
- Exploiter
-
Ajouter des catégories et tags
- Enrichir le Front Matter dans
content/blog/
aveccategories
ettags
, afficher un nuage de tags dans le template
- Enrichir le Front Matter dans
-
Introduire des données structurées
- Fournir explicitement les informations d’article en JSON-LD pour viser les rich results
8. Conclusion
- Créez un dépôt sur GitHub, reliez-le à Cloudflare Pages, configurez GitHub OAuth pour DecapCMS et associez votre domaine personnalisé : votre site est prêt
- Les articles se créent depuis
/admin/
→ commit sur GitHub → déploiement automatique par Cloudflare - Pour les templates et le design, travaillez en local avec
hugo server
, puis poussez vos changements via Git
Vous disposez désormais d’un environnement de blog sans serveur basé sur Hugo + GitHub + Decap CMS + Cloudflare, prêt pour la production.
Nous avons ainsi obtenu une structure de site à coût d’exploitation minimal. Le revers de la médaille : en l’état, la recherche interne, les catégories ou encore les nuages de tags restent modestes face aux blogs clés en main. Je vais continuer à enrichir tout cela pas à pas, main dans la main avec ChatGPT.