Jak krok za krokem postavit serverless blog s vlastní doménou a CMS na stacku Hugo + GitHub + Decap CMS + Cloudflare Pages
Tento web jsem kdysi provozoval na vlastní doméně přes původní Google Sites z éry Google Apps. Když Google starou službu celosvětově ukončil, nechal jsem https://www.ixam.net dlouho ležet ladem. Rozhodl jsem se, že doménu konečně znovu využiji – ideálně v serverless architektuře, která nebude záviset na službě, jež může kdykoli bez varování skončit. A tak jsem se do obnovy pustil společně s ChatGPT.
Myslel jsem si, že s ChatGPT to půjde snadno, ale realita byla jiná. Pořád jsem zapomínal na základní požadavky, při troubleshootingu jsme se točili v kruhu a nakonec jsem musel pročítat repozitáře jako https://github.com/patrickgrey/website/, abychom se posunuli dál. Výsledek je přesto společným dílem člověka a AI.
Byl to pocit, jako když vedete velmi schopného, ale často zbloudilého kolegu zpět na trať – což jen ukazuje, jak daleko už se AI dostala. Jakmile se však téma komplikovalo, frustrace z drobných chyb rostla.
Poučení? Místo nekonečného vlákna, ve kterém má AI držet všechny předpoklady v hlavě, je lepší se zastavit, sám si věci uspořádat a začít nové čisté vlákno s čerstvými instrukcemi.
Přesto bych bez generativní AI nezvládl tolik rešerší, pokusů a učení – její přínos pro produktivitu je obrovský.
Cíl
- Blog musí být dostupný na
https://www.example.com/
- Do Decap CMS se bude možné přihlásit na
https://www.example.com/admin/
a psát články - Nové příspěvky se commitují do GitHubu a automaticky nasazují na Cloudflare
- Dodatečné provozní náklady zůstávají nulové (poplatek za registraci domény je mimo záběr tohoto návodu)
0. Předpoklady a struktura složek
0-1. Předpoklady
-
Doména (např.
example.com
) je už zaregistrovaná -
Můžete pracovat na Windows i macOS (ukázky příkazů pro obě platformy)
-
Budeme používat tyto služby (stačí bezplatné tarify)
- GitHub účet
- Cloudflare účet (pro Pages)
- Git (lokálně)
- Hugo (pro lokální náhledy i buildy na Cloudflare)
0-2. Ukázková struktura repozitáře
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
Tato struktura plní následující role:
hugo.toml
— Globální konfigurace webu (baseURL
nahraďte produkční adresou)functions/api/*.js
— Cloudflare Pages Functions (/api/auth
a/api/callback
pro GitHub OAuth)layouts/_default/*.html
— Šablony pro Hugostatic/admin/
— UI a konfigurace Decap CMSstatic/css/main.css
— Vzhled (CSS)static/_headers
— Volitelné HTTP hlavičky pro Cloudflare Pages
V návodu zvýrazňuji místa, která je nutné upravit podle vlastního prostředí, jako „kontrolní body k nahrazení“.
1. Příprava
1-1. Založte si GitHub účet
- V prohlížeči přejděte na GitHub a dokončete registraci
1-2. Založte si Cloudflare účet
- V prohlížeči přejděte na Cloudflare a dokončete registraci
1-3. Nainstalujte Git a 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. Hlavní postup (až po první nasazení)
2-1. Připravte si repozitář lokálně
-
Na GitHubu založte prázdný repozitář
- Cesta v UI: GitHub > New repository
- Například:
my-hugo-blog
-
Naklonujte repozitář do počítače
-
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
-
-
Do kořene repozitáře zkopírujte složky a soubory z výše uvedené struktury
Kontrolní body k nahrazení (nezapomeňte upravit)
-
hugo.toml
baseURL = "https://www.example.com" # ← nahraďte produkční URL (např. 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 # ← nahraďte vlastním repozitářem na GitHubu branch: master # ← jméno výchozí větve (main nebo master) base_url: https://www.example.com # ← nahraďte produkční URL auth_endpoint: /api/auth # ← endpoint pro Functions (neměňte) media_folder: static/uploads public_folder: /uploads
Poznámka:
functions/api/auth.js
afunctions/api/callback.js
stačí ponechat, jak jsou. Díkyurl.origin
neobsahují žádné hodnoty závislé na prostředí.
-
První commit a push
-
Windows
PS C:\work\my-hugo-blog> git add -A PS C:\work\my-hugo-blog> git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + 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](https://decapcms.org/) + CF Pages" mac:~/work/my-hugo-blog dev$ git push -u origin master
-
2-2. Vytvořte GitHub OAuth App (pro přihlášení do Decap CMS)
-
V UI: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App
-
Vyplňte:
- Application name:
[DecapCMS](https://decapcms.org/) for my-hugo-blog
- Homepage URL:
https://www.example.com
- Authorization callback URL:
https://www.example.com/api/callback
← zásadní údaj
- Application name:
-
Po vytvoření si poznamenejte:
- Client ID
- Client Secret (vygenerujte a bezpečně uložte)
Tyto hodnoty uložíte jako proměnné prostředí v Cloudflare Pages.
2-3. Založte projekt v Cloudflare Pages
-
V UI: Cloudflare dashboard > Pages > Create a project > Connect to Git
-
Propojte GitHub a vyberte repozitář
my-hugo-blog
-
Nastavte build:
-
Framework preset:
None
(nebo nechte Cloudflare autodetekci) -
Build command:
hugo
-
Build output directory:
public
-
Proměnné prostředí:
HUGO_VERSION
=0.128.0
(příklad – doporučuji sladit s lokální verzí)GITHUB_CLIENT_ID
= (z předchozího kroku)GITHUB_CLIENT_SECRET
= (z předchozího kroku)
-
-
Klikněte na Save and Deploy a počkejte na první nasazení
- Po úspěchu získáte náhledovou adresu
*.pages.dev
- Po úspěchu získáte náhledovou adresu
2-4. Přiřaďte vlastní doménu www.example.com
-
V UI: Cloudflare > Pages > (daný projekt) > Custom domains > Set up a custom domain
-
Zadejte
www.example.com
a přidejte ji -
Pokud nemáte doménu převedenou pod Cloudflare:
- Zapište si DNS záznamy, které Cloudflare zobrazí (např.
CNAME www -> <project>.pages.dev
) - V rozhraní svého registrátora nastavte odpovídající záznamy přesně podle pokynů (postup závisí na registrátorovi)
- Zapište si DNS záznamy, které Cloudflare zobrazí (např.
-
Po propagaci ověřte, že
https://www.example.com/
načte váš web
Tip: Pokud doménu spravujete přímo v Cloudflare, nastaví se potřebné DNS záznamy jedním klikem.
2-5. Přihlaste se do administrace Decap CMS
- Otevřete v prohlížeči
https://www.example.com/admin/
- Klikněte na tlačítko typu „Login with GitHub“ a povolte GitHub OAuth
- Při prvním přístupu GitHub zobrazí „Authorize this app?“ – potvrďte kliknutím na Authorize
Pokud přihlášení selže, znovu zkontrolujte Callback URL v OAuth aplikaci a proměnné prostředí
GITHUB_CLIENT_ID/SECRET
v Cloudflare.
3. Provoz (tvorba článků, synchronizace, úprava šablon)
3-1. Vytváření článků přes CMS
- Rozhraní:
https://www.example.com/admin/
- V levém menu vyberte Blog → New blog
- Vyplňte
Title
,Publish Date
,Description
,Body
- Stiskněte Publish – Decap CMS provede commit do GitHubu a Cloudflare automaticky nasadí změny
Vzniklé Markdown soubory najdete v
content/blog/
.
3-2. Aktualizujte lokální repozitář (po publikaci přes CMS)
-
Windows
PS C:\work\my-hugo-blog> git pull origin master
-
macOS
mac:~/work/my-hugo-blog dev$ git pull origin master
Takto si stáhnete nejnovější články a můžete bezpečně upravovat šablony či CSS.
3-3. Lokální úpravy designu a šablon
-
Spusťte lokální vývojový server
-
Windows
PS C:\work\my-hugo-blog> hugo server -D
-
macOS
mac:~/work/my-hugo-blog dev$ hugo server -D
-
Náhled zobrazíte na
http://localhost:1313/
-
-
Co můžete měnit (příklady)
layouts/_default/baseof.html
—<head>
, hlavička, patičkalayouts/_default/index.html
— výpis „Nejnovější články“ na úvodní stráncelayouts/_default/single.html
— tělo článkustatic/css/main.css
— barvy, písmo, odsazení
-
Commitujte a pushujte změny
-
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
-
Po pár desítkách sekund až minut Cloudflare změny automaticky nasadí
-
3-4. Tip k práci s branchí
- Dbejte na to, aby výchozí větev (
main
nebomaster
) byla shodně nastavená vconfig.yml
pro Decap CMS i v nastavení Cloudflare - Pokud chcete náhled před nasazením, vytvořte Pull Request na GitHubu – Cloudflare Pages automaticky připraví Preview prostředí
4. Užitečné doplňky (volitelné)
4-1. Příklad static/_headers
(zakáže cache pro administraci)
/admin/*
Cache-Control: no-store
4-2. robots a sitemap (podle potřeby)
- Připravte
static/robots.txt
pro řízení indexace - V
hugo.toml
můžete v sekcioutputs
zapnout generování RSS a sitemap
5. Časté problémy a řešení
- CMS se při loginu jen točí
→ Zkontrolujte, zda má GitHub OAuth správné Callback URLhttps://www.example.com/api/callback
a jestli proměnnéGITHUB_CLIENT_ID/SECRET
v Cloudflare obsahují správné hodnoty - Úvodní stránka funguje, ale články vracejí 404
→ Ověřte, žebaseURL
vhugo.toml
odpovídá skutečné doméně. V build logu Cloudflare sledujte, zda vzniká složkapublic/
- /admin je prázdné
→ Prověřte, jestli načítání Decap CMS vestatic/admin/index.html
neblokuje prohlížeč nebo rozšíření; zkuste je vypnout a načíst stránku znovu
6. Přehled kontrolních míst (rekapitulace)
Soubor / nastavení | Klíč | Co nastavit (příklad) |
---|---|---|
hugo.toml |
baseURL |
https://www.example.com |
static/admin/config.yml |
repo |
<YOUR_GH_USERNAME>/my-hugo-blog |
static/admin/config.yml |
branch |
Nastavte na main nebo master podle reality |
static/admin/config.yml |
base_url |
https://www.example.com |
Cloudflare Pages | HUGO_VERSION |
Např. 0.128.0 |
Cloudflare Pages | GITHUB_CLIENT_ID |
Client ID z GitHub OAuth |
Cloudflare Pages | GITHUB_CLIENT_SECRET |
Client Secret z GitHub OAuth |
GitHub OAuth App | Callback URL | https://www.example.com/api/callback |
Cloudflare Pages | Custom domain | Přidejte www.example.com |
7. Přílohy: ukázkové soubory (nahraďte vlastními údaji)
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 a výkon: co doladit po zprovoznění
Po spuštění blogu můžete vylepšit pozice ve vyhledávání i rychlost načítání těmito kroky:
-
Sitemap a robots.txt
- Zaregistrujte
public/sitemap.xml
, který Hugo generuje, do Google Search Console - Do
static/robots.txt
doplňte pravidla pro indexaci
- Zaregistrujte
-
OGP a Twitter Cards
- Do
layouts/_default/baseof.html
přidejte<meta property="og:title">
,<meta property="og:image">
apod. - Sdílené odkazy na sociálních sítích pak vypadají lépe
- Do
-
Konverze obrázků do WebP
- Využijte
resources/_gen/images/
a pipeline v Hugu pro automatický převod do WebP - Výrazně tím zrychlíte načítání
- Využijte
-
Kategorie a tagy
- Do Front Matter souborů v
content/blog/
přidejtecategories
atags
, v šablonách můžete zobrazit tag cloud
- Do Front Matter souborů v
-
Strukturovaná data
- Pomocí JSON-LD popište články pro vyhledávače a získejte šanci na rich results
8. Shrnutí
- Založte repozitář na GitHubu, napojte ho na Cloudflare Pages, nastavte GitHub OAuth pro Decap CMS a přiřaďte vlastní doménu – tím je základ hotový
- Články vytvářejte na
/admin/
→ commitují se do GitHubu → Cloudflare je nasadí - Šablony a design upravujte lokálně s
hugo server
, změny nasazujte přes Git
Takto získáte kompletní serverless prostředí pro blog na stacku Hugo + GitHub + Decap CMS + Cloudflare.
Základní kostru s minimálními provozními náklady máme. Vyhledávání, kategorie, tagy a tag cloud zatím zaostávají za běžnými blogy, ale to je momentálně daň za rychlost. Postupně je chci rozšiřovat společně s ChatGPT.