Pyöritin tätä sivustoa aiemmin omaan verkkotunnukseen liitettynä Google Sitesin klassisessa versiossa (silloinen Google Apps). Kun Google lopetti palvelun maailmanlaajuisesti vuonna 2021, jätin https://www.ixam.net-osoitteen pitkäksi aikaa hunningolle. Lopulta päätin, että verkkotunnus on otettava taas hyötykäyttöön. Halusin serverittömän arkkitehtuurin, jota ei sido äkillisesti alas ajettava alusta, ja ryhdyin rakentamaan sivuston uusiksi ChatGPT:n kanssa.

Kuvittelin, että ChatGPT tekisi projektista helpon, mutta todellisuus oli toinen. Vaadittuja ehtoja unohtui, vianhaku kiersi kehää ja jouduin selaamaan esimerkiksi https://github.com/patrickgrey/website/-repositorya, jotta pääsin tilanteista eteenpäin. Lopputulos oli silti yhteinen ponnistus.

Kokemus muistutti lahjakasta mutta usein harhaan ajautuvaa tiimikaveria, jota pitää varovasti ohjata takaisin raiteilleen – osoitus siitä, kuinka hyödylliseksi tekoäly on jo kehittynyt. Samalla mitä monimutkaisemmaksi asiat menivät, sitä helpommin turhautuminen kasvoi virheisiin.

Oma johtopäätökseni: sen sijaan että yrittää pakottaa tekoälyn kantamaan kaiken kontekstin yhdessä keskustelussa, on parempi pysähtyä, jäsentää tilanne itse ja aloittaa uusi säie selkeillä ohjeilla.

En silti olisi yksin pystynyt samaan tutkinta-, kokeilu- ja opiskelumäärään. Generatiivinen tekoäly on hurja tuottavuuskerroin.


Tavoite

  • Blogi palvelee osoitteessa https://www.example.com/
  • Kirjautuminen Decap CMS-järjestelmään onnistuu osoitteessa https://www.example.com/admin/, ja siellä voi luoda artikkeleita
  • Julkaistut kirjoitukset commitoidaan GitHubiin ja Cloudflare ottaa ne automaattisesti käyttöön
  • Lisäkäyttökustannukset: 0 euroa (verkkotunnusmaksu oli olemassa ennestään)

0. Esivaatimukset ja hakemistorakenne

0-1. Esivaatimukset

  • Verkkotunnus (example.com) on jo rekisteröity

  • Käyttöjärjestelmäksi kelpaa Windows tai macOS (molemmille on komentoesimerkit)

  • Tarvittavat palvelut (ilmaisversio riittää)

0-2. Esimerkkirepositorion rakenne

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

Tällä rakenteella on seuraavat roolit:

  • hugo.toml — sivuston laajuiset asetukset (korvaa baseURL tuotantourlilla)
  • functions/api/*.js — Cloudflare Pages Functions -päät (/api/auth ja /api/callback GitHub OAuthia varten)
  • layouts/_default/*.html — Hugon templatet
  • static/admin/Decap CMS:n käyttöliittymä ja asetukset
  • static/css/main.css — ulkoasu
  • static/_headers — valinnaiset HTTP-otsakkeet Cloudflare Pagesille

Tässä ohjeessa merkitsen ympäristökohtaiset korvauskohdat “korvaa nämä” -tarkistuslistoilla.


1. Valmistelut

1-1. Luo GitHub-tili

  • Rekisteröidy GitHubiin selaimen kautta

1-2. Luo Cloudflare-tili

  • Rekisteröidy Cloudflareen selaimessa

1-3. Asenna Git ja 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. Varsinainen käyttöönotto (ensimmäiseen deployhin saakka)

2-1. Valmistele repositorio paikallisesti

  1. Luo tyhjä repository GitHubiin

    • Polku: GitHub > New repository
    • Esimerkinimi: my-hugo-blog
  2. Kloonaa se paikallisesti

    • 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. Sijoita edellä kuvattu kansio- ja tiedostokokonaisuus repositorion juureen

Korvaa nämä (pakolliset muutokset)

  • hugo.toml

    baseURL = "https://www.example.com"   # ← korvaa tuotantourlilla (esim. 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   # ← korvaa omalla GitHub-repositorillasi
      branch: master                          # ← oletushaaran nimi (main tai master)
      base_url: https://www.example.com       # ← korvaa tuotantourlilla
      auth_endpoint: /api/auth                # ← function-päätepiste (vakio)
    media_folder: static/uploads
    public_folder: /uploads
    

Huomio: functions/api/auth.js ja functions/api/callback.js ovat ympäristöriippumattomia, koska ne käyttävät url.origin -arvoa. Niitä ei tarvitse muokata.

  1. Tee ensimmäinen commit ja push

    • 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
      

Tallenna seuraavassa vaiheessa tarvittavat arvot Cloudflare Pagesin ympäristömuuttujiin.


2-2. Luo GitHub OAuth -sovellus (Decap CMS-kirjautumista varten)

  1. Polku: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App

  2. Syötä tiedot:

    • Application name: DecapCMS for my-hugo-blog
    • Homepage URL: https://www.example.com
    • Authorization callback URL: https://www.example.com/api/callbacktärkeä
  3. Kun sovellus on luotu, talleta seuraavat arvot:

    • Client ID
    • Client Secret (luo uusi ja tallenna se)

Nämä arvot määritetään Cloudflare Pagesin ympäristömuuttujiksi.


2-3. Luo Cloudflare Pages -projekti

  1. Polku: Cloudflare-hallintapaneeli > Pages > Create a project > Connect to Git

  2. Yhdistä GitHub ja valitse my-hugo-blog -repository

  3. Build-asetukset:

    • Framework preset: None (tai anna Cloudflaren tunnistaa automaattisesti)

    • Build command: hugo

    • Build output directory: public

    • Ympäristömuuttujat:

      • HUGO_VERSION = 0.128.0 (esimerkki – käytä samaa versiota kuin paikallisesti)
      • GITHUB_CLIENT_ID = edellisessä kohdassa saatu arvo
      • GITHUB_CLIENT_SECRET = edellisessä kohdassa saatu arvo
  4. Valitse Save and Deploy ja odota ensimmäistä buildia

    • Onnistunut ajo tuottaa *.pages.dev-esikatseluosoitteen

2-4. Ota käyttöön oma verkkotunnus www.example.com

  1. Polku: Cloudflare > Pages > (projekti) > Custom domains > Set up a custom domain

  2. Syötä www.example.com

  3. Jos verkkotunnus ei ole kokonaan Cloudflaren hallinnassa:

    • Kopioi Cloudflaren näyttämät DNS-tietueet (esim. CNAME www -> <project>.pages.dev)
    • Lisää samat tietueet rekisterinpitäjän ohjeiden mukaan
  4. Kun DNS-muutos on levinnyt, varmista että https://www.example.com/ näyttää sivuston

Vinkki: jos verkkotunnus on jo Cloudflaressa, tarvittavat DNS-tietueet lisätään yhdellä klikkauksella.


2-5. Kirjaudu Decap CMS-hallintaan

  1. Avaa https://www.example.com/admin/
  2. Valitse Login with GitHub ja hyväksy OAuth-oikeudet
  3. Ensimmäisellä kerralla GitHub pyytää Authorize-vahvistuksen – hyväksy se

Jos kirjautuminen epäonnistuu, tarkista OAuth callback URL sekä Cloudflaren GITHUB_CLIENT_ID- ja GITHUB_CLIENT_SECRET -ympäristömuuttujat.


3. Sisällöntuotanto ja ylläpito (kirjoittaminen, synkronointi, templatet)

3-1. Luo artikkeleita CMS:n kautta

  1. Siirry osoitteeseen https://www.example.com/admin/
  2. Vasemmasta navigaatiosta: BlogNew blog
  3. Täytä kentät Title, Publish Date, Description ja Body
  4. Valitse Publish, jolloin muutos commitoituu GitHubiin → Cloudflare julkaisee sen automaattisesti

Generoidut Markdown-tiedostot ilmestyvät hakemistoon content/blog/.

3-2. Hae uusimmat muutokset paikalliseen repositorioon CMS:stä julkaisemisen jälkeen

  • Windows

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

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

Näin pidät paikallisen repositorion synkassa ja voit turvallisesti muokata templateja tai CSS:ää.

3-3. Muokkaa ulkoasua ja templateja paikallisesti

  1. Käynnistä paikallinen kehityspalvelin

    • Windows

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

      mac:~/work/my-hugo-blog dev$ hugo server -D
      
    • Avaa selainosoite http://localhost:1313/ esikatselua varten

  2. Yleisimmät muokkauskohdat

    • layouts/_default/baseof.html<head>-sisältö, header ja footer
    • layouts/_default/index.html — etusivun uusimmat kirjoitukset
    • layouts/_default/single.html — artikkelin runkopohja
    • static/css/main.css — värit, typografia, välit
  3. Commitoi ja pushaa muutokset

    • 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
      
    • Cloudflare julkaisee muutoksen sekunneissa tai minuuteissa

3-4. Haarojen hallinnan vinkkejä

  • Varmista, että oletushaara (main tai master) on sama sekä static/admin/config.yml -tiedostossa että Cloudflare-projektin asetuksissa
  • Kun avaat GitHubissa pull requestin, Cloudflare Pages luo automaattisesti esikatseluympäristön

4. Valinnaisia parannuksia

4-1. Esimerkki static/_headers -tiedostosta (estetään välimuisti hallintanäkymästä)

/admin/*
  Cache-Control: no-store

4-2. Robots ja sivukartta

  • Toimita static/robots.txt hakukoneiden hallintaan
  • Lisää tarvittaessa outputs-osio hugo.toml -tiedostoon, jos haluat esimerkiksi RSS:n tai sivukartat

5. Yleiset kompastuskivet ja ratkaisut

  • CMS kirjaa sisään loputtomasti
    → Varmista, että GitHub OAuthin callback URL on täsmälleen https://www.example.com/api/callback ja että Cloudflaren GITHUB_CLIENT_ID ja GITHUB_CLIENT_SECRET on asetettu oikein
  • Etusivu toimii, mutta artikkelisivut palauttavat 404:n
    → Tarkista, että hugo.toml-tiedoston baseURL vastaa oikeaa domainia. Katso Cloudflaren build-logeista, että public/ generoitui
  • /admin näyttää tyhjän sivun
    → Varmista, etteivät selaimen laajennukset tai CSP estä static/admin/index.html -tiedoston viittaamia Decap CMS -skriptejä; poista laajennukset käytöstä ja lataa sivu uudelleen

6. “Korvaa nämä” -yhteenvetotaulukko

Tiedosto / asetus Avain Esimerkkiasetus
hugo.toml baseURL https://www.example.com
static/admin/config.yml repo <YOUR_GH_USERNAME>/my-hugo-blog
static/admin/config.yml branch Sama kuin oletushaara (main/master)
static/admin/config.yml base_url https://www.example.com
Cloudflare Pages HUGO_VERSION esim. 0.128.0
Cloudflare Pages GITHUB_CLIENT_ID GitHub OAuth -sovelluksen client ID
Cloudflare Pages GITHUB_CLIENT_SECRET GitHub OAuth -sovelluksen client secret
GitHub OAuth -sovellus Callback URL https://www.example.com/api/callback
Cloudflare Pages Custom domain Lisää www.example.com

7. Liite: mallipohjat (korvaa merkityt kohdat)

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- ja suorituskykyvinkkejä

Kun blogi toimii, seuraavat vaiheet auttavat hakunäkyvyyden ja latausnopeuden parantamisessa:

  1. Sivukartat ja robots.txt

    • Lähetä Hugon automaattisesti tuottama public/sitemap.xml Google Search Consoleen
    • Räätälöi hakurobottien käyttäytymistä static/robots.txt -tiedostolla
  2. OGP (Open Graph) ja Twitter-kortit

    • Lisää tageja kuten <meta property="og:title"> ja <meta property="og:image"> tiedostoon layouts/_default/baseof.html
    • Paranna jaettavien linkkien ulkoasua
  3. Kuvien muuttaminen WebP-muotoon

    • Hyödynnä resources/_gen/images/ -hakemistoa ja Hugo Pipes -putkia WebP-automaatiota varten
    • Nopeuta sivun latautumista
  4. Kategoriat ja tagit

    • Lisää categories- ja tags-kentät content/blog/-hakemiston front matteriin ja näytä ne templaatissa esimerkiksi tagipilvenä
  5. Strukturoitu data

    • Toimita artikkelien metadata JSON-LD-muodossa ja kasvata rich result -mahdollisuuksia

8. Yhteenveto

  • Luo GitHub-repositorio, kytke se Cloudflare Pagesiin, määritä GitHub OAuth Decap CMS:ää varten ja liitä oma verkkotunnus – siinä on koko käyttöönotto
  • Tekijät julkaisevat /admin/-näkymästä → commit GitHubiin → Cloudflare julkaisee automaattisesti
  • Viimeistele templatet ja ulkoasu paikallisesti komennolla hugo server, tee commit ja pushaa Gitillä

Tämän verran riittää serverittömän blogin pyörittämiseen Hugo-, GitHub-, Decap CMS- ja Cloudflare-pinolla.

Sain nyt haluamani ultrakevyen perustan, vaikka ominaisuudet kuten sivuhaku, kategoriat, tagit ja tagipilvet laahaavat vielä isoja blogialustoja perässä. Tarkoitus on laajentaa kokonaisuutta askel kerrallaan, tekoälyn auttamana.