Úvod do static-site generátoru Hugo – tvorba webu s blogem

Generátory statických stránek se v poslední době dostávají do popředí. V jednom z nich si dnes napíšeme jednoduchoučký firemní web s blogem. To štěstí bude mít generátor Hugo.

Huga používáme pro svůj firemní web a pro několik dalších projektů, na kterých jsme ať už jako firma nebo jako jednotlivci pracovali. Jeho největší výhodou je rychlost a robustnost (disponuje množstvím užitečných funkcí bez nutnosti psát vlastní řešení).

Skvěle se hodí pro větší weby nebo blogy s tisíci nebo desetitisíci články a stránkami. Také ho nativně podporuje Forestry (o propojení připravujeme další článek).

Dalšími známými generátory jsou Eleventy, Gatsby nebo Jekyll. Skvělé srovnání sepsal Mark Thomas Miller ve svém článku.

Principy Huga se vám pokusím předvést na modelovém příkladu – jednoduchém webu s blogem. Vše tak bude snazší na pochopení. Na konci článku najdete odkaz do repozitáře s kompletním kódem.

Jak začít

Hugo je napsaný v jazyce Go a je open-source. Vy si však můžete stáhnout jediný binární soubor, který budete následně používat. Postup se bude lišit podle operačního systému, na němž pracujete.

Homebrew (Linux a macOS)

Ujistěte se, že máte nainstalovaný Homebrew. Poté spusťte terminál a zadejte následující příkaz. Tím je Hugo nainstalovaný a můžete se přesunout k vytvoření projektu.

$ brew install hugo

Windows

Můžete využít package manager Chocolatey. Následně nainstalujte tzv. rozšířenou verzi Hugo (abyste mohli využívat opravdu všechny funkce, např. Sass kompilaci) následujícím příkazem.

$ choco install hugo-extended -confirm

Další možnosti

Pokud nemůžete nebo nechcete využít výše uvedené možnosti instalace, lze ručně zkompilovat zdrojový kód Huga nebo použít jiné balíčkovací systémy. V tomto případě postupujte podle příslušného návodu na stránce Install Hugo.

Vytvoření a struktura projektu

Pro tento krok doporučuji použít terminál. Přesuňte se do složky, ve které chcete vytvořit nový projekt v Hugovi a použijte následující příkaz.

$ hugo new site .

Můžete přidat flag --force v případě, že máte ve složce již nějaký obsah (např. README.md nebo jiné soubory z git repozitáře). Při jeho použití nebude brán zřetel na to, že složka není kompletně prázdná.

Tento příkaz vytvoří základní výchozí adresářovou strukturu. Ve vašem adresáři se vytvoří následující složky, případně soubory (u každé položky zároveň popíšu, k čemu slouží).

  • archetypes – obsahuje markdown (.md) soubory se strukturou metadat v jednotlivých typech stránek (např. blogproject nebo teammember), hodí se, pokud nové stránky vytváříte v terminálu
  • content – adresář pro vlastní data jednotlivých stránek; strukturu a názvy složek a souborů v tomto adresáři je možné použít jako jeden ze způsobů pro „generování“ URL adres stránek webu
  • data – složka pro datafiles; může posloužit pro oddělení stránek od obsahu, který je v některých místech webu generován, osobně jsem tuto složku ještě nepoužil
  • layouts – místo pro strukturu webu, jeho částí a dalších souborů, které jsou generovány (např. sitemap.xml nebo robots.txt); společně se složkou content tvoří dvojici těch nejdůležitějších
  • static – sem umístíme statické soubory, které následně budou na webu použity (externí JS knihovny, logo, soubory ke stažení aj.)
  • themes – umožňuje vytvořit několik různých template webu a poté specifikovat, která se má použít, my budeme vytvářet jen jednu a tu umístíme do složky layouts; tuto složku můžete s klidem na duši odstranit
  • config.toml – konfigurační soubor projektu pro Hugo; bude podrobněji rozebrán níže

Existují další speciální adresáře – třeba i18n nebo assets. Některé z nich vám přiblížím v tomto článku, jiné budu snad moci popsat někdy v budoucnosti vždy s nějakou ukázkou.

Přidání prvního obsahu

Nejprve si vytvoříme nějaký základní obsah, abychom měli data, s nimiž budeme následně moci pracovat a zobrazovat je. Našim cílem je nyní tedy složka content.

Vytvořme si v ní podsložku blog a následující soubory. U každého souboru uvedu krátký text, abyste se nezačali ztrácet.

content/index.md

Toto bude úvodní stránka našeho webu. Její URL adresa bude /. Do metadat markdown souboru přidáme prozatím hodnotu pouze pro title.

---
title: Úvodní stránka
---

content/blog/_index.md

V tomto souboru budeme mít obsah týkající se stránky s výpisem všech článků. Všimněte si, že soubor začíná podtržítkem. Níže se dozvíte, na co má toto podtržítko vliv. URL adresa bude /blog/.

---
title: Blog
---

content/blog/clanek-a.md

Toto je náš první článek v blogu. Opět přidáme do metadat hodnotu pro title atribut. Kromě toho přidáme odstavec textu, který poté budeme vypisovat v detailu článku. URL: /blog/clanek-a/.

---
title: Článek A
---

Toto je **první článek** na našem blogu. Věříme, že se vám bude líbit a přečtete si ho od začátku do konce. Moc nám na tom totiž záleží. Předem děkujeme všem čtenářům, že jste s námi.

content/blog/clanek-b.md

Druhý článek v blogu. Nijak významně se neliší od toho prvního. Jedinou změnou je vlastní text a URL adresa článku: /blog/clanek-b/.

---
title: Článek B 
---

Toto je pro změnu **druhý článek**. Zde uvádíme jiný text, abyste si na první pohled všimli, že se jedná o zcela jiný obsah a nacházíme se tak na jiné stránce. I tento článek si můžete přečíst, pokud vás zajímá.

URL adresy

U výše uvedených souborů obsahu jste si již možná všimli, jak Hugo generuje URL adresy jednotlivých stránek. V podstatě je kopírována struktura složky content s tím, že je vynechána přípona souborů .md.

Zpětně tak můžeme říci, že například stránka /tym/vedeni/emil-bzuk/ odebírá obsah ze souboru content/tym/vedeni/emil-bzuk.md.

Vlastní URL

Kromě této možnosti lze využít url parametr v meta tazích .md souborů. Pokud za parametr title v článku B přidáme url: /specialni/adresa-clanku/, bude tato adresa upřednostněna před generováním ze struktury.

Soubor content/blog/clanek-b.md by tedy vypadal následovně:

---
title: Článek B 
url: /specialni/adresa-clanku/
---

Toto je pro změnu **druhý článek**. Zde uvádíme jiný text, abyste si na první pohled všimli, že se jedná o zcela jiný obsah a nacházíme se tak na jiné stránce. I tento článek si můžete přečíst, pokud vás zajímá.

Meta obsah (front matter) v .md

V markdownových souborech můžeme uchovávat i meta obsah, který byl předveden výše. Od klasického obsahu je také příslušně oddělen – zde například třemi pomlčkami pro yaml syntaxi.

Atributy title i url jsou takzvaně vyhrazené. Tyto vyhrazené atributy jsou definovány přímo v dokumentaci Huga a mají obvykle nějaký speciální účinek. Zde je seznam vyhrazených parametrů.

Například u title je speciálním účinkem už jen to, že je možné ho vypsat použitím následující syntaxe: {{ .Title }}. U url je zase speciálním účinkem jasné finální nadefinování URL adresy, kterou má daná stránka po buildu mít.

Přidávat můžete i libovolné vlastní parametry. K takovým budete následně přistupovat syntaxí {{ .Params.mujatribut }}.

Příprava partials

Složku content ponechme na chvíli stranou a přesuňme se do layouts. Zde vytvoříme základní HTML strukturu webu. Začneme speciálními částmi kódu nebo webu – takzvanými partials. Stejně se jmenuje i složka, do které budeme jednotlivé partials umisťovat.

layouts/partials/head.html

V tomto souboru bude HTML hlavička. V té připojíme i CSS styl. Patří sem také SEO HTML tagy aj. Běžný obsah <head> tagu. Soubor style.css si můžete stáhnout zde. Umístěte ho do složky static/css/style.css.

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ .Title }}</title>
    <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="/css/style.css">
</head>

layouts/partials/header.html

V našem ukázkovém webu se bude v tomto souboru nacházet pouze hlavní menu. V běžných případech by se tu jistě nacházelo také logo firmy. Později tento kód s menu nahradíme kódem pro automatické generování menu.

<header class="o-header">
    <nav class="o-nav">
        <ul class="o-nav__list">
            <li class="o-nav__item"><a href="/" class="o-nav__link">Domů</a>
            </li>
            <li class="o-nav__item"><a href="/o-nas/" class="o-nav__link">O nás</a>
            </li>
            <li class="o-nav__item"><a href="/blog/" class="o-nav__link">Blog</a>
            </li>
            <li class="o-nav__item"><a href="/kontakt/" class="o-nav__link">Kontakt</a>
            </li>
        </ul>
    </nav>
</header>

layouts/partials/footer.html

Jednoduše patička stránky, nic více, nic méně. Aby byla alespoň něčím zajímavá, vygeneruje při buildu u copyrightu aktuální rok. Více o tom, jak funguje v Hugovi formát data, se dočtete zde.

<footer class="o-footer">
    &#169; Copyright {{ dateFormat "2006" now }} Hugovinky
</footer>

Kostra HTML a homepage

Renderování kódu v Hugovi funguje v běžných případech tak, že „vstupním“ souborem je soubor layouts/_default/baseof.html. V něm jsou následně includovány nějaké základní partials a poté je řečeno, kde se má vyrenderovat samotný obsah stránky. Soubor si nyní vytvoříme.

layouts/_default/baseof.html

Na řádku číslo 3 říkáme, že chceme vložit partial, která se jmenuje head. Na tomto místě tedy bude obsah souboru layouts/partials/head.html.

Na řádcích 5 a 9 jsou to partials header a footer, opět stejnojmenné soubory ze složky layouts/partials.

Na řádku 7 poté říkáme, že chceme vyrenderovat blok main pro stránku, na které se aktuálně nacházíme. Kde tento blok vznikne se dozvíte hned v dalším souboru, kterým bude úvodní stránka.

<!DOCTYPE html>
<html lang="cs">
{{ partial "head" . }}
<body>
    {{ partial "header" . }}
    <main class="o-main">
        {{ block "main" . }}{{ end }}
    </main>    
    {{ partial "footer" . }}
</body>
</html>

layouts/_default/home.html

Tento soubor Hugovi říká, jak má vypadat homepage webu. Často bývá totiž nastylována oproti ostatním stránkám jinak. Ve svém home.html souboru vytvoříme blok zmiňovaný v předchozím souboru – main.

{{ define "main" }}
    <h1>Vítejte v Hugovinkách</h1>
{{ end }}

Těchto bloků můžete mít samozřejmě několik (různě pojmenovaných) pod sebou. Osobně často používám druhý blok scripts, který je v baseof.html vložen před uzavírající tag </body>.

Běžné stránky

Pokud jste už pomysleli na stránky jako „O nás“ nebo „Kontakt“, nyní se k nim dostáváme. Běžné stránky jsou generovány podle souboru single.html. Ten nyní vytvoříme po boku baseof.html a home.html.

Vytvoření případných obsahových souborů nechám na vás, v repozitáři s kompletním kódem budou už však vytvořené i obsahové soubory. Aby vše korespondovalo s URL adresami, které jsme uvedli v menu v partial header.html, pro jistotu uvedu cesty k těmto dvěma souborům.

  • o nás – content/o-nas.md
  • kontakt – content/kontakt.md

layouts/_default/single.html

Do souboru umístíme například následující kód. Hlavní nadpis h1 tak bude stejný, jako titulek stránky. Můžete si ale ve front matter (v metadatech v .md souborech ve složce content) vytvořit atribut heading a ten vypsat použitím {{ .Params.heading }}.

{{ define "main" }}
    <h1>{{ .Title }}</h1>
    {{ .Content }}
{{ end }}

Blog

Nyní budou všechny stránky (včetně článků a naopak kromě homepage) renderovány podle souboru single.html z předchozího kroku.

My však chceme zaprvé vytvořit speciální stránku pro výpis všech článků v blogu a zadruhé v detailu článku uvádět informace jako je autor, datum vydání nebo přibližná doba čtení článku (kterou vám Hugo sám sdělí, no není to skvělé?).

Nyní se dostáváme k tomu, jak pro určité části webu definovat jiné soubory layoutu. V tomto článku využijeme názornější možnost – budeme přesně kopírovat strukturu složky content. Když tedy chceme pro blog definovat jinou strukturu HTML kódu, vytvoříme si ve složce layouts složku blog. Tam vytvoříme nový soubor single.html.

layouts/blog/single.html

Tento soubor bude (v případě, že se nacházíme na stránce, jejíž obsah čerpáme z content/blog/*) upřednostněn před obecným (výchozím) souborem layouts/_default/single.html.

Všimněte si na řádku 4 kódu {{ .Content }}. Ten na dané místo vloží HTML kód, který je vygenerován z obsahu markdownového souboru (pod metadaty). U každého článku je jeden odstavec s čátečně tučným textem. Ten tam můžeme nyní očekávat.

{{ define "main" }}
    <h1>{{ .Title }}</h1>
    <div class="c-blogpost">
        {{ .Content }}
    </div>
{{ end }}

layouts/blog/list.html

To bude název souboru pro výpis článků blogu. Všimněte si, že se jmenuje list.html, což jsme doposud nepoužili. Obsah bude čerpán ze souboru content/blog/_index.md. Užitečná tu však budou pouze metadata, protože zbytek obsahu bude kompletně generovaný.

Pod kódem popíšu jednotlivé kroky, které se tu dějí. Nelekejte se toho, jak složitě soubor na první pohled vypadá.

{{ define "main" }}
    <h1>Blog</h1>
    <div class="c-bloglist">
        {{ $pages := where .Site.RegularPages "Type" "blog" }}
        {{ range $pages.ByDate.Reverse }}
            <div class="c-bloglist__item">
                <h2 class="c-bloglist__heading">
                    <a href="{{ .RelPermalink }}" class="c-bloglist__headinglink">{{ .Title }}</a>
                </h2>
                <div class="c-bloglist__metas">
                    {{ .Params.author }} &bull; {{ .PublishDate.Format "2. 1. 2006" }}
                </div>
                <div class="c-bloglist__summary">
                    {{ .Summary }}
                </div>
                <a href="{{ .RelPermalink }}" class="c-bloglist__link">Číst článek →</a>
            </div>
        {{ end }}
    </div>
{{ end }}

Na prvním řádku opět vytváříme blok s názvem main. Ten začíná hlavním nadpisem a pokračuje elementem div, v němž budou generovány další – pro každý článek jeden.

Na čtvrtém řádku vytváříme proměnnou $pages, v níž budou uchovávány všechny stránky, jejichž typ je roven blog. Tento typ je „vyčten“ z názvu složky, v němž se nacházejí dané soubory obsahu. Soubory ve složce content/blog mají tedy nastaven typ blog. Tento typ je možné přepsat předdefinovanou proměnnou type, což u takto malého projektu ale nedoporučuji.

Na dalším řádku začíná cyklus, který postupně projde všechny stránky uchovávané v proměnné $pages seřazené podle data, od nejnovějšího ($pages.ByDate.Reverse). Řazení podle data funguje na principu další předdefinované proměnné v metadatech, a sice publishDate. Ta má i další význam – pokud datum není v přítomnosti nebo minulosti, nebude při buildu daná stránka sestavena a zveřejněna (stane se tak pouze v neprodukčním prostředí).

Dále je box rozdělen na nadpis (který je zároveň odkazem na článek), naše vlastní metadata týkající se článku (autor a datum vydání), shrnutí a další odkaz na článek.

V odkazu na článek v nadpisu vás bude zajímat {{ .RelPermalink }}. Ten obsahuje relativní URL adresu z kořenového adresáře, tedy například /blog/clanek-a/. Samotný text odkazu je poté {{ .Title }} jako jsme použili už na jiných stránkách dříve. Hodnotou je tedy hodnota parametru title v metadatech v .md souboru článku.

Dále je vypsáno jméno autora článku {{ .Params.author }} a datum vydání článku {{ .PublishDate.Format "2. 1. 2006" }} ve formátu D. M. YYYY. Hodnota .PublishDate je rovna publishDate v metadatech .md souboru daného článku.

Pod informacemi o autorovi a datu vydání je shrnutí článku (summary). To generuje sám Hugo z obsahu pod metadaty markdownového souboru. Délku {{ .Summary }} si můžete nastavit v konfiguračním souboru config.toml (více zde).

Dále najdeme jen další odkaz na článek (tentokrát zobrazený formou tlačítka) s již výše popsaným {{ .RelPermalink }}.

Doplnění chybějících dat u článků

Jistě jste si povšimli, že nikde neuvádíme autora článku ani datum vydání. Tato data nyní přidáme do souborů obsahu článků.

content/blog/clanek-a.md

---
title: Článek A
author: Jan Čuněk
publishDate: 2020-11-04T22:41:00.000+02:00
---

Toto je **první článek** na našem blogu. Věříme, že se vám bude líbit a přečtete si ho od začátku do konce. Moc nám na tom totiž záleží. Předem děkujeme všem čtenářům, že jste s námi.

content/blog/clanek-b.md

---
title: Článek B 
url: /specialni/adresa-clanku/
author: Václav Kukačka
publishDate: 2019-04-02T15:25:11.000+02:00
---

Toto je pro změnu **druhý článek**. Zde uvádíme jiný text, abyste si na první pohled všimli, že se jedná o zcela jiný obsah a nacházíme se tak na jiné stránce. I tento článek si můžete přečíst, pokud vás zajímá.

Nyní se budou u článků správně zobrazovat také informace o autorovi a datum, kdy byl článek na webu zveřejněn.

Spuštění v dev módu

Nuže, tímto je naše dílo prozatím dokončeno. Následující příkaz spouští huga ve vývojovém prostředí. Server zůstává běžet a při jakékoliv úpravě kódu se vám web sám aktualizuje. Výchozí adresa webu v dev módu je http://localhost:1313.

$ hugo server -D

Finální build webu

Chcete-li web již sestavit pro produkční prostředí, poslouží vám níže uvedený příkaz. Flag --minify zajistí minifikaci generovaného kódu, flag --gc pak maže po buildu různé vytvořené soubory z cache. Sestavený web najdete ve složce public.

$ hugo --gc --minify

Celý popis příkazu hugo.

Závěr

Dnes jsme si vytvořili malinký web s funkčním blogem pomocí jednoho z nejoblíbenějších (a oprávněně) generátorů statických stránek jménem Hugo. Jak jsem slíbil, přikládám sem odkaz na repozitář s kompletním kódem (včetně stránek „O nás“ a „Kontakt“).

Máte-li nějaké dotazy nebo si nevíte rady s vlastním projektem, budu rád, když necháte pod článkem komentář.