Seit dem 11. Mai 2026 läuft einfach-aleks.com nicht mehr über IONOS, sondern über Cloudflare Pages. Der alte Pfad — hexo generate lokal bauen, hexo deploy schiebt die Files per SFTP auf einen IONOS-Hoster, davor ein Cloudflare-Proxy als CDN — ist Geschichte. Der neue Pfad ist git push origin main und der Rest passiert von alleine.
Klingt unspektakulär, war aber überfällig. Es gab einen Grund der mich überzeugt hat das Wochenende dafür zu opfern, und einen Bonus-Grund den ich im Nachhinein zugeben muss.
Warum überhaupt etwas ändern
Der Grund: ein abgekündigter SSH-Stack im Deploy-Pfad
Hexo hat ein offizielles SFTP-Deployer-Plugin (hexo-deployer-sftp) und ich hab es seit der WordPress-zu-Hexo-Migration 2021 benutzt. Das Dependabot-Alert hat mich nicht erst 2026 darauf hingewiesen — ich hab es jahrelang einfach weggeklickt. Typisches Entwicklerproblem: “mache ich irgendwann mal später”. Im Mai 2026 hab ich dann endlich genauer hingeschaut. Das Plugin hat seit Februar 2020 keinen Release mehr gesehen. Es bringt ein veraltetes [email protected] mit, das eine OS-Command-Injection-Lücke hat (CVE-2020-26301). Patch existiert ab [email protected], aber der Upgrade-Pfad ist tot — die Bibliothek dazwischen (sftp-sync-deploy) ist seit Dezember 2018 unmaintained.
Das heißt: zwei tote Pakete, ein offener High-Severity-CVE, kein realistischer Fix-Weg. Entweder selber forken und upgraden — was bei einem Hobby-Blog kein vernünftiger Aufwand ist — oder den ganzen SFTP-Pfad rauswerfen.
Bonus-Grund: Atomic Deploys
Bei SFTP überträgt man Files einzeln. Während der Upload läuft, ist die Seite in einem Mischzustand: neue HTML-Files referenzieren CSS-Files die noch nicht da sind, ein Mobil-User bekommt 404 auf ein Bild das gerade hochgeladen wird. Realistisch ein Risiko-Fenster von 10–20 Sekunden pro Deploy. Bei einem Hobby-Blog mit ein paar Hundert Lesern pro Tag fällt das niemandem auf, aber elegant ist es nicht.
Atomic Deploys heißt: Cloudflare lädt den kompletten Build hoch, validiert ihn, und schaltet erst dann den Edge-Router auf die neue Version um. Null Übergangszustand, keine halbfertigen Seiten.
Was ich mir angeschaut habe
Bevor ich Cloudflare Pages genommen hab, war kurz die Frage: gibt’s einen besseren Kandidaten?
Die üblichen Alternativen sind Netlify, Vercel oder selbst hosten auf einem VPS. Meine Anforderungen waren:
- Free-Tier muss für einen Blog mit deutlich unter 100k Page-Views/Monat reichen
- Custom Domain mit Apex (einfach-aleks.com, nicht nur www.) ohne Workarounds
- HTTPS automatisch
- Atomic Deploys
- Build im eigenen CI, nicht im Anbieter-CI (ich will pnpm-Caching, kontrollierte Node-Version, eigene Tests vor Deploy)
Netlify und Vercel sind beide solide, aber DNS und CDN wären weiter bei Cloudflare und Pages/Workers ist mein anderer Stack ohnehin — den Anbieter zu konzentrieren wo möglich ist mir lieber.
Was Cloudflare Pages für mich entschieden hat:
- Die DNS-Zone ist eh schon in meinem Cloudflare-Account. CNAME-Flattening am Apex erspart den ganzen “wie zeige ich
einfach-aleks.comohne A-Record auf irgendwas”-Tanz. - Free-Tier liegt bei 500 Builds/Monat, 20.000 Files pro Projekt, unlimited Bandwidth. Mein Blog: 443 Files, deutlich unter 100 Deploys/Monat. Faktor 5–40 Headroom.
- Auto-Cache-Purge bei jedem Deploy. Das räumt alte Versionen am Edge in einem Rutsch weg, ohne dass ich im Dashboard händisch nachhelfen muss.
Der Schnitt
Die eigentliche Cutover-Sequenz, kurz:
- Cloudflare-API-Token erzeugen mit
Account → Pages → EditundAccount-Settings → Read. Kein Global-API-Key, ein scoped Token. Den habe ich als GitHub-SecretCLOUDFLARE_API_TOKENinsproduction-Environment gelegt, dazu noch die Account-ID. - Pages-Projekt anlegen als Direct-Upload-Projekt (NICHT “Connect to Git” — ich will dass mein eigenes CI baut, nicht das Cloudflare-CI). Production-Branch
main. - Workflow umbauen. Der GitHub-Actions-Workflow läuft jetzt so:
1 | name: Deploy |
Vorher gab es eine pnpm run deploy-Stufe die SFTP-Credentials aus einer .env injiziert hat. Die ist komplett raus. hexo-deployer-sftp aus den Dependencies geflogen, pnpm-lock.yaml schrumpft um die ganze ssh2-Familie.
DNS-Cutover. Im Cloudflare-Dashboard “Custom Domain” am Pages-Projekt:
einfach-aleks.comundwww.einfach-aleks.comdazu. Cloudflare hat die alten A/AAAA-Records auf den IONOS-Origin selber entfernt und CNAME-Flattening am Apex eingerichtet. SSL-Zertifikat (Google Trust Services, gültig bis August 2026) wurde innerhalb von etwa fünf Minuten provisioniert.Smoke und Soak.
pnpm run smokeeinmal grün, dann acht Stunden zugucken ob irgendwas Komisches passiert. Tat es nicht. Am 12. Mai hab ich die altenHEXO_DEPLOY_SFTP_*-Secrets aus dem GitHub-Environment gelöscht und den Feature-Branch entfernt.
Was jetzt anders ist
Drei Sachen die im Alltag spürbar sind:
Edge-Cache läuft automatisch sauber. Jeder Deploy invalidiert die CDN-Edges global, gemessene Propagation unter 30 Sekunden. Auch der _headers-File im Repo, den ich später ergänzt habe, kann pro Pfad-Pattern eigene Cache-Times setzen — fingerprinted CSS/JS immutable, Feeds 5 Minuten, llms.txt 1 Stunde, indexnow.txt 60 Sekunden.
Pipeline-Gates sind ehrlich. Vor dem Deploy laufen jetzt zwingend:
pnpm run test— Frontmatter-Validierung (UUID, Datum, Kategorie) und Build-Smokepnpm run build— der eigentliche Hexo-Buildpnpm run lint:links— checkt alle Links die nach außen gehen
Wenn einer davon rot wird, kommt nichts auf Production. Vorher hab ich die Tests separat in einer ci.yml gehabt, aber der deploy.yml hat sie nicht ausgeführt — wenn ich einen Tippfehler gemacht hab und nichts gegen den main gepusht hab außer dem Post, ging der Deploy trotzdem durch. Jetzt nicht mehr.
Search-Engine-Ping fährt mit. Nach dem Pages-Deploy läuft ein hexo ping, das per IndexNow Bing/Yandex/Naver/Seznam/Yep über die neue/aktualisierte URL informiert, parallel via XML-RPC Pingomatic und Twingly anfunkt, und über WebSub den RSS-Hub pubsubhubbub.appspot.com benachrichtigt. Hatte ich vorher nicht — kein WordPress-Update-Services-Äquivalent. Das hab ich im Zuge der Migration mitgebaut, weil’s eh schon die Pipeline aufgemacht hat.
Der gesamte CI-Lauf (install + test + build + link-check + deploy + ping) liegt bei rund 90 Sekunden. Davon ist der reine Pages-Upload zwei bis drei Sekunden — public/ mit etwa 440 Files ist nichts für einen modernen CDN. Der Rest ist pnpm-install (gecacht), Hexo-Build und die Link-Validierung.
Performance-mäßig: nach der Migration und einer kleinen Lighthouse-Sanierungsrunde (CLS war auf Mobile-Slow-4G noch 1.09, jetzt 0.00) treffen alle sieben Hauptseitentypen 100/100/100/100 auf Mobile und Desktop. Hat mit der Migration selbst nichts zu tun — die alte IONOS-Variante war schon schnell — aber die saubere Pipeline hat die Iteration deutlich angenehmer gemacht.
Was es gekostet hat
Damit niemand glaubt das ist umsonst:
- Cloudflare-Account und Pages-Projekt: Free. Kreditkarte nicht hinterlegt.
- Cloudflare-API-Token: kostenlos, aber muss man einmal sauber scopen. Wer den Global-API-Key nimmt, hat in seinen GitHub-Secrets effektiv einen Account-Master-Key liegen — bitte nicht machen.
- Zeitaufwand: ungefähr ein Tag verteilt über zwei Abende. Davon ein Großteil Recherche und Plan schreiben; die eigentliche Code-Änderung war eine Stunde, die DNS-Umstellung fünf Minuten.
- Eine kleine Sünde: der Workflow hat einen ENV-Schalter
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true". Hintergrund:cloudflare/wrangler-action@v3läuft intern auf Node.js 20, und GitHub deprecated Node 20 auf den Runnern ab Juni 2026 mit Komplettentfernung im September. Bis es einenwrangler-action@v4mit nativem Node 24 gibt, ist der Force-Flag der Interim-Fix. Ist seit Mai 2026 als Open-Issue beim Action-Repo bekannt. Der Flag fliegt raus sobald v4 da ist. - Backup-Schmerz: der alte IONOS-Origin liegt noch ein paar Wochen brach. Mein letzter Hexo-Build von dort ist als Cold-Backup auf einer externen Platte. Wenn nach drei Monaten nichts auffällt, baue ich den Hoster ab.
Außerdem hab ich package.json von einem "deploy"-Skript befreit, das niemand mehr braucht. git push origin main ist die einzige Aktion mit Production-Effekt. Wer einen lokalen Preview will, läuft pnpm run build && pnpm dlx wrangler pages deploy public/ — geht, brauche ich aber nie.
Was ich beim nächsten Mal anders machen würde
Sachen versuchen sofort zu erledigen.
Der Blog ist seit einer Dekade mein Werkzeug für genau diese Art von Aufräum-Übungen. Das hier war eine davon.