Serverittömän blogin rakentaminen omalla verkkotunnuksella Hugo + GitHub + Decap CMS + Cloudflare Pages -pinossa
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ää)
- GitHub-tili
- Cloudflare-tili (Pages-käyttöä varten)
- Git paikallisesti
- Hugo (paikalliseen esikatseluun ja Cloudflaren buildiin)
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 (korvaabaseURL
tuotantourlilla)functions/api/*.js
— Cloudflare Pages Functions -päät (/api/auth
ja/api/callback
GitHub OAuthia varten)layouts/_default/*.html
— Hugon templatetstatic/admin/
— Decap CMS:n käyttöliittymä ja asetuksetstatic/css/main.css
— ulkoasustatic/_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
-
Luo tyhjä repository GitHubiin
- Polku: GitHub > New repository
- Esimerkinimi:
my-hugo-blog
-
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
-
-
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
jafunctions/api/callback.js
ovat ympäristöriippumattomia, koska ne käyttäväturl.origin
-arvoa. Niitä ei tarvitse muokata.
-
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)
-
Polku: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App
-
Syötä tiedot:
- Application name:
DecapCMS for my-hugo-blog
- Homepage URL:
https://www.example.com
- Authorization callback URL:
https://www.example.com/api/callback
← tärkeä
- Application name:
-
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
-
Polku: Cloudflare-hallintapaneeli > Pages > Create a project > Connect to Git
-
Yhdistä GitHub ja valitse
my-hugo-blog
-repository -
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 arvoGITHUB_CLIENT_SECRET
= edellisessä kohdassa saatu arvo
-
-
Valitse Save and Deploy ja odota ensimmäistä buildia
- Onnistunut ajo tuottaa
*.pages.dev
-esikatseluosoitteen
- Onnistunut ajo tuottaa
2-4. Ota käyttöön oma verkkotunnus www.example.com
-
Polku: Cloudflare > Pages > (projekti) > Custom domains > Set up a custom domain
-
Syötä
www.example.com
-
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
- Kopioi Cloudflaren näyttämät DNS-tietueet (esim.
-
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
- Avaa
https://www.example.com/admin/
- Valitse Login with GitHub ja hyväksy OAuth-oikeudet
- Ensimmäisellä kerralla GitHub pyytää Authorize-vahvistuksen – hyväksy se
Jos kirjautuminen epäonnistuu, tarkista OAuth callback URL sekä Cloudflaren
GITHUB_CLIENT_ID
- jaGITHUB_CLIENT_SECRET
-ympäristömuuttujat.
3. Sisällöntuotanto ja ylläpito (kirjoittaminen, synkronointi, templatet)
3-1. Luo artikkeleita CMS:n kautta
- Siirry osoitteeseen
https://www.example.com/admin/
- Vasemmasta navigaatiosta: Blog → New blog
- Täytä kentät
Title
,Publish Date
,Description
jaBody
- 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
-
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
-
-
Yleisimmät muokkauskohdat
layouts/_default/baseof.html
—<head>
-sisältö, header ja footerlayouts/_default/index.html
— etusivun uusimmat kirjoituksetlayouts/_default/single.html
— artikkelin runkopohjastatic/css/main.css
— värit, typografia, välit
-
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
taimaster
) 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
-osiohugo.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älleenhttps://www.example.com/api/callback
ja että CloudflarenGITHUB_CLIENT_ID
jaGITHUB_CLIENT_SECRET
on asetettu oikein - Etusivu toimii, mutta artikkelisivut palauttavat 404:n
→ Tarkista, ettähugo.toml
-tiedostonbaseURL
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:
-
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
- Lähetä Hugon automaattisesti tuottama
-
OGP (Open Graph) ja Twitter-kortit
- Lisää tageja kuten
<meta property="og:title">
ja<meta property="og:image">
tiedostoonlayouts/_default/baseof.html
- Paranna jaettavien linkkien ulkoasua
- Lisää tageja kuten
-
Kuvien muuttaminen WebP-muotoon
- Hyödynnä
resources/_gen/images/
-hakemistoa ja Hugo Pipes -putkia WebP-automaatiota varten - Nopeuta sivun latautumista
- Hyödynnä
-
Kategoriat ja tagit
- Lisää
categories
- jatags
-kentätcontent/blog/
-hakemiston front matteriin ja näytä ne templaatissa esimerkiksi tagipilvenä
- Lisää
-
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.