Un portfolio servi depuis le réseau de Cloudflare
Ce site est une application Nuxt 3 dont toutes les pages sont pré-rendues au build : l'accueil, les pages de CV et chacun des articles du blog. Cloudflare Pages est un hébergement idéal pour ce cas : HTML servi depuis le réseau mondial de Cloudflare, HTTPS automatique, un déploiement à chaque push, et un aperçu par branche. Voici la configuration, et surtout les trois pièges rencontrés lors de la dernière refonte.
La configuration du build
Dans le tableau de bord (Workers et Pages → votre projet → Paramètres → Build) :
- Commande de build :
npm run build - Répertoire de sortie :
dist - Branche de production :
master(oumain)
Nuxt détecte automatiquement l'environnement Cloudflare Pages et utilise le preset Nitro cloudflare-pages : les routes déclarées au prérendu deviennent des fichiers HTML statiques dans dist/, et le reste est servi par un Worker. Pour un site 100 % statique, déclarez les routes à pré-rendre dans nuxt.config.ts. Les générer depuis vos données évite d'oublier un article :
import { blogArticles } from './data/blog'
const staticPages = ['/', '/experience', '/skills', '/projects', '/education', '/blog']
const blogPages = blogArticles.map(article => `/blog/${article.slug}`)
export default defineNuxtConfig({
nitro: {
prerender: {
routes: [...staticPages, ...blogPages],
crawlLinks: true,
},
},
})
Le même tableau alimente le sitemap : un nouvel article est automatiquement pré-rendu et référencé, sans rien toucher d'autre.
Piège n°1 : la branche de production
Après la refonte, poussée sur master, le site en ligne n'avait pas changé. Le build avait pourtant réussi. En réalité, la branche de production du projet était restée sur une ancienne branche de travail : chaque push sur master ne produisait qu'un déploiement d'aperçu, sur une URL *.pages.dev, sans toucher au domaine principal.
À vérifier dans Paramètres → Build → Contrôle de branche. Et changer la branche de production ne redéploie rien : il faut un nouveau commit sur cette branche (ou relancer un déploiement) pour que la production se mette à jour.
Piège n°2 : la version de Node
Nuxt 3.21, Vite 7 et Nitro exigent Node ^20.19 ou >=22.12. Le système de build de Cloudflare lit la version dans un fichier .node-version ou .nvmrc à la racine du dépôt (ou dans la variable d'environnement NODE_VERSION). Un simple 20 est ambigu : fixez une version majeure récente, clairement compatible.
echo 22 > .node-version
echo 22 > .nvmrc
Piège n°3 : le lockfile et npm ci
Le build échouait en quelques secondes avec ce message :
npm error `npm ci` can only install packages when your package.json and
package-lock.json or npm-shrinkwrap.json are in sync.
npm error Missing: [email protected] from lock file
npm error Missing: [email protected] from lock file
...
Pourtant, npm install et le build passaient parfaitement en local. La cause : le package-lock.json avait été généré avec npm 11 (livré avec Node 24), alors que Cloudflare installe les dépendances avec npm ci en npm 10. Les deux versions ne résolvent pas les dépendances optionnelles de la même façon, et npm 10 considérait le lockfile comme désynchronisé.
La correction : régénérer le lockfile avec la même version de npm que la CI, déclarée dans le champ packageManager du package.json :
npx [email protected] install --package-lock-only
Et surtout, reproduire la CI en local avant de pousser, sur une copie propre du dépôt :
git clone . /tmp/ci-check && cd /tmp/ci-check
npx [email protected] ci
npm run build
Si ces deux commandes passent, le build Cloudflare passera aussi.
Les redirections avec slash final
Une fois en ligne, curl -I https://benmacha.tn/experience renvoie un 308 vers /experience/. Ce n'est pas une erreur : chaque page pré-rendue est un fichier experience/index.html, et Cloudflare Pages redirige vers l'URL de répertoire. Pour le SEO, gardez des liens internes et des URL canoniques cohérents avec ce comportement, pour éviter une redirection à chaque clic.
Suivre un déploiement sans ouvrir le tableau de bord
Cloudflare publie l'état de chaque build sur GitHub, sous forme de check run attaché au commit. Avec la CLI GitHub, on suit le déploiement depuis le terminal :
gh api repos/MOI/MON-REPO/commits/$(git rev-parse HEAD)/check-runs \
--jq '.check_runs[] | select(.name=="Cloudflare Pages") | "\(.status) \(.conclusion)"'
Le résultat passe de in_progress à completed success, ou completed failure. Dans ce dernier cas, le lien details_url du check mène directement aux logs du build.
En résumé
- Vérifiez que la branche de production est bien celle sur laquelle vous poussez
- Fixez la version de Node dans
.node-version, compatible avec vos dépendances - Générez le lockfile avec la même version de npm que la CI, et testez
npm cien local - Générez les routes pré-rendues et le sitemap depuis vos données
Une fois ces points réglés, le cycle devient idéal : un git push, une minute de build, et le site est à jour partout dans le monde.