Ezt a webhelyet korábban egy saját domainhez kötött, a Google Apps (a mai Google Workspace elődje) részét képező klasszikus Google Sites-szal üzemeltettem. Amikor a Google 2021-ben világszerte lekapcsolta ezt a régi szolgáltatást, a https://www.ixam.net domain hosszú időre parlagon maradt. Úgy döntöttem, itt az ideje újra életet lehelni a címbe. A cél egy olyan szerver nélküli architektúra volt, amelyet nem köt egyetlen, bármikor megszűnő platformhoz sem, és a megvalósítást a ChatGPT-vel közösen vittem végig.

Azt hittem, hogy a ChatGPT mellett ez gyerekjáték lesz, de hamar kiderült, hogy korántsem ennyire egyszerű. Sorra felejtettük el a követelményeket, a hibakeresés körbe-körbe forgott, és végül olyan repókat is át kellett néznem, mint a https://github.com/patrickgrey/website/, hogy tovább tudjunk lépni. Kemény munka volt, de végül közös erővel befejeztük.

Az élmény olyan volt, mintha egy rendkívül tehetséges, ám gyakran félrecsúszó csapattársat terelgetnék vissza a sínre – ami egyben azt is jelenti, hogy az MI immár valóban hasznos társ. Ugyanakkor minél összetettebb lett a feladat, annál könnyebb volt frusztrálódni a hibák miatt.

A tanulságom: ahelyett, hogy egyetlen beszélgetésben próbálnánk minden előzményt a fejében tartani, jobb megállni, saját kezűleg összefoglalni a jelenlegi állapotot, majd tiszta lappal, új utasításokkal indítani egy friss threadet.

Mindezek ellenére ennyi kutatást, próbálkozást és tanulást egyedül biztosan nem tudtam volna végigvinni. A generatív MI elképesztő termelékenységnövelő.


Cél

  • A blog a https://www.example.com/ címen fusson
  • A Decap CMS kezelőfelületét a https://www.example.com/admin/ alatt el lehessen érni, és ott lehessen bejegyzéseket készíteni
  • A létrehozott cikkek GitHubra kerüljenek, és onnan automatikusan települjenek a Cloudflare-re
  • A folyamatos üzemeltetés többletköltsége maradjon nullán (a domain regisztrációs díja eleve adott)

0. Előfeltételek és mappastruktúra

0-1. Előfeltételek

  • A domain (például example.com) már regisztrált

  • Windows vagy macOS egyaránt megfelel (mindkettőhöz mutatok parancspéldákat)

  • A használt szolgáltatások (ingyenes csomagban is elegendők)

0-2. Példa repós struktúrára

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

Ez a felépítés az alábbi szerepeket fedi le:

  • hugo.toml — a teljes oldal konfigurációja (a baseURL értékét cseréld a végleges URL-re)
  • functions/api/*.js — Cloudflare Pages Functions (GitHub OAuth-hoz az /api/auth és /api/callback végpontok)
  • layouts/_default/*.html — Hugo sablonok
  • static/admin/ — a Decap CMS felülete és beállításai
  • static/css/main.css — kinézet
  • static/_headers — opcionális HTTP-fejlécek a Cloudflare Pageshez

A leírásban mindenhol „Cseréld ki” jelzőt teszek oda, ahol környezetspecifikus módosításra van szükség.


1. Előkészületek

1-1. GitHub-fiók létrehozása

  • Jelentkezz be a GitHubra a böngészőből, és készíts új fiókot

1-2. Cloudflare-fiók létrehozása

  • Böngészőből regisztrálj a Cloudflare-re

1-3. Git és Hugo telepítése

  • 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. Fő lépések (az első telepítésig)

2-1. A repó előkészítése helyben

  1. Hozz létre üres repót a GitHubon

    • Menüpont: GitHub > New repository
    • Név példa: my-hugo-blog
  2. Klónozd le a gépedre

    • 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
      
  3. Másold be a fenti mappastruktúrát közvetlenül a repó gyökerébe

„Cseréld ki” ellenőrzőlista (kötelező módosítások)

  • hugo.toml

    baseURL = "https://www.example.com"   # ← cseréld a saját éles URL-re (példa: 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   # ← cseréld a saját GitHub repódra
      branch: master                          # ← a fő ág neve (main vagy master)
      base_url: https://www.example.com       # ← cseréld a saját éles URL-re
      auth_endpoint: /api/auth                # ← funkció végpontja (fix)
    media_folder: static/uploads
    public_folder: /uploads
    

Megjegyzés: a functions/api/auth.js és functions/api/callback.js fájlok környezetfüggetlenek, mert url.origin-t használnak, így közvetlen módosításra nincs szükség.

  1. Készítsd el az első commitot, majd pushold

    • Windows

      PS C:\work\my-hugo-blog> git add -A
      PS C:\work\my-hugo-blog> git commit -m "Initial commit: Hugo + DecapCMS + 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 + CF Pages"
      mac:~/work/my-hugo-blog dev$ git push -u origin master
      

A következő lépésben felsorolt értékeket a Cloudflare Pages felületén környezeti változókba mentsd el.


2-2. GitHub OAuth App létrehozása (a Decap CMS belépéshez)

  1. Menüpont: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App

  2. Töltsd ki:

    • Application name: DecapCMS for my-hugo-blog
    • Homepage URL: https://www.example.com
    • Authorization callback URL: https://www.example.com/api/callbackkulcsfontosságú
  3. A létrehozás után jegyezd fel:

    • Client ID
    • Client Secret (generálj újat és mentsd el)

Ezeket az értékeket a Cloudflare Pages környezeti változóiként fogod használni.


2-3. Cloudflare Pages projekt létrehozása

  1. Menüpont: Cloudflare dashboard > Pages > Create a project > Connect to Git

  2. Kapcsold össze a GitHubot, és válaszd ki a my-hugo-blog repót

  3. Build beállítások:

    • Framework preset: None (vagy hagyd, hogy a Cloudflare felismerje)

    • Build command: hugo

    • Build output directory: public

    • Környezeti változók:

      • HUGO_VERSION = 0.128.0 (példa – legyen összhangban a helyi verzióval)
      • GITHUB_CLIENT_ID = az előző szakaszban rögzített érték
      • GITHUB_CLIENT_SECRET = az előző szakaszban rögzített érték
  4. Kattints a Save and Deploy gombra, és várd meg az első buildet

    • A sikeres futások *.pages.dev előnézeti URL-t adnak

2-4. A www.example.com egyedi domain beállítása

  1. Menüpont: Cloudflare > Pages > (projekt) > Custom domains > Set up a custom domain

  2. Add meg: www.example.com

  3. Ha a domaint nem a Cloudflare kezeli teljesen:

    • Másold ki a Cloudflare által mutatott DNS-rekord adatokat (például CNAME www -> <project>.pages.dev)
    • Állítsd be ezeket a regisztrátornál a dokumentációjuk szerint
  4. A DNS frissülése után ellenőrizd, hogy a https://www.example.com/ cím a site-ot szolgálja ki

Tipp: ha a domaint már a Cloudflare kezeli, egy kattintás az összes szükséges DNS-rekordot létrehozza.


2-5. Bejelentkezés a Decap CMS adminfelületre

  1. Nyisd meg a https://www.example.com/admin/ oldalt
  2. Kattints a Login with GitHub gombra, majd hagyd jóvá az OAuth kérést
  3. Első alkalommal a GitHub kérni fogja az Authorize jóváhagyást – engedélyezd

Ha a bejelentkezés nem sikerül, ellenőrizd az OAuth callback URL-t, valamint a Cloudflare GITHUB_CLIENT_ID és GITHUB_CLIENT_SECRET környezeti változóit.


3. Üzemeltetés (tartalomkészítés, szinkron, sablonok)

3-1. Bejegyzés készítése a CMS-ből

  1. Lépj be a https://www.example.com/admin/ felületre
  2. A bal oldali menüben válaszd a BlogNew blog elemet
  3. Töltsd ki a Title, Publish Date, Description és Body mezőket
  4. Kattints a Publish gombra – ez GitHub commitot hoz létre, amit a Cloudflare automatikusan telepít

Az elkészült Markdown fájlok a content/blog/ mappába kerülnek.

3-2. Tartalom lehúzása helyben, ha a CMS-ből publikáltál

  • Windows

    PS C:\work\my-hugo-blog> git pull origin master
    
  • macOS

    mac:~/work/my-hugo-blog dev$ git pull origin master
    

Így a helyi repó naprakész marad, és biztonságosan szerkesztheted a sablonokat vagy a CSS-t.

3-3. Dizájn és sablon finomhangolása helyben

  1. Indítsd el a helyi fejlesztői szervert

    • Windows

      PS C:\work\my-hugo-blog> hugo server -D
      
    • macOS

      mac:~/work/my-hugo-blog dev$ hugo server -D
      
    • Nyisd meg a http://localhost:1313/ címet a böngészőben az előnézethez

  2. Gyakori testreszabási pontok

    • layouts/_default/baseof.html<head> tartalom, fejléc, lábléc
    • layouts/_default/index.html — legfrissebb bejegyzések listája a főoldalon
    • layouts/_default/single.html — cikkoldal sablonja
    • static/css/main.css — színek, betűk, margók
  3. Commit és push

    • 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
      
    • A Cloudflare néhány másodpercen vagy percen belül újra telepít

3-4. Ágkezelési tippek

  • Ellenőrizd, hogy az alapértelmezett ág (main vagy master) azonos legyen a static/admin/config.yml fájlban és a Cloudflare projektben
  • Ha GitHubon pull requestet nyitsz, a Cloudflare Pages automatikusan létrehoz egy előnézeti környezetet

4. Hasznos kiegészítések (opcionális)

4-1. Példa static/_headers beállításra (adminfelület cache tiltása)

/admin/*
  Cache-Control: no-store

4-2. Robots és sitemap

  • Készíts static/robots.txt fájlt a robotok szabályozásához
  • A hugo.toml outputs szekciójába felveheted az RSS-t, sitemapot vagy más kimeneteket

5. Gyakori buktatók és megoldások

  • A CMS-bejelentkezés végtelen ciklusba kerül
    → Ellenőrizd, hogy a GitHub OAuth callback URL pontosan https://www.example.com/api/callback, és a Cloudflare GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET változói helyesek
  • A főoldal működik, de a cikkoldalak 404-et adnak
    → Győződj meg róla, hogy a hugo.toml baseURL értéke megegyezik az éles domainnel. Nézd meg a Cloudflare buildlogjait, hogy a public/ mappa tényleg létrejött-e
  • Az /admin oldal üres
    → Ellenőrizd, hogy a static/admin/index.html által hivatkozott Decap CMS-szkripteket nem blokkolja-e böngészőbővítmény vagy CSP; kapcsold ki a kiegészítőket és töltsd újra

6. „Cseréld ki” ellenőrzőlista (összefoglaló)

Fájl / beállítás Kulcs Példaérték / teendő
hugo.toml baseURL https://www.example.com
static/admin/config.yml repo <YOUR_GH_USERNAME>/my-hugo-blog
static/admin/config.yml branch Egyezzen a fő ággal (main vagy master)
static/admin/config.yml base_url https://www.example.com
Cloudflare Pages HUGO_VERSION pl. 0.128.0
Cloudflare Pages GITHUB_CLIENT_ID GitHub OAuth App client ID
Cloudflare Pages GITHUB_CLIENT_SECRET GitHub OAuth App client secret
GitHub OAuth App Callback URL https://www.example.com/api/callback
Cloudflare Pages Egyedi domain Add hozzá a www.example.com címet

7. Melléklet: mintafájlok (csak a jelölt részeket cseréld)

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. SEO- és teljesítménytippek

Miután a blog élesben fut, az alábbi lépések javíthatják a keresési láthatóságot és a betöltési sebességet:

  1. Sitemap és robots.txt

    • Regisztráld a Hugo által generált public/sitemap.xml fájlt a Google Search Console-ban
    • Szabályozd a robotokat a static/robots.txt szerkesztésével
  2. OGP (Open Graph Protocol) és Twitter-kártyák

    • Adj hozzá olyan tageket, mint <meta property="og:title"> vagy <meta property="og:image"> a layouts/_default/baseof.html fájlban
    • Így a közösségi megosztások is látványosabbak lesznek
  3. Képek WebP-re alakítása

    • Használd a resources/_gen/images/ könyvtárat és a Hugo Pipes funkciót az automatikus WebP-konverzióhoz
    • Gyorsabb oldalbetöltés
  4. Kategóriák és tagek hozzáadása

    • A content/blog/ alatti front matterben töltsd ki a categories és tags mezőket, és jelenítsd meg például címkefelhőként a sablonokban
  5. Strukturált adatok használata

    • Adj meg JSON-LD formátumú metaadatokat a cikkekhez, hogy nagyobb eséllyel jelenjenek meg rich results találatok

8. Összegzés

  • Hozz létre egy GitHub-repót, kapcsold a Cloudflare Pageshez, állítsd be a Decap CMS GitHub OAuthot, és rendeld hozzá az egyedi domaint – ezzel készen vagy
  • A szerzők az /admin/ felületen publikálnak → GitHub commit → Cloudflare automatikus deploy
  • A sablonokat és dizájnt helyben a hugo server segítségével finomíthatod, majd Git push-sal viszed élesbe

Ezekkel a lépésekkel már működtethető a Hugo + GitHub + Decap CMS + Cloudflare alapú szerver nélküli blog.

A célt elértem: minimális üzemeltetési költségű alapot kaptam. Ugyanakkor a belső kereső, a kategóriák vagy a címkefelhő még elmarad a nagy blogplatformoktól. A terv az, hogy ezeket is fokozatosan bővítsem – továbbra is ChatGPT-vel közösen dolgozva.