Proč jsem si na marketingový web napsal vlastní SSG místo Astro

Pro marketingový web ve 20 jazycích je Astro nebo Next overkill. Pár tisíc řádků esbuildu a tsx udělá totéž — a rozumím každému řádku.

Proč jsem si na marketingový web napsal vlastní SSG místo Astro

Kdy je plný framework zbytečný

Při návrhu webové prezentace pro celý projekt Weather InTouch jsem stál před klasickým rozhodnutím, jakou technologii zvolit pro frontend. Web má v zásadě jednoduchou strukturu: domovskou stránku, ceník, stránky jednotlivých funkcí, právní dokumenty a blog. Typů stránek je tedy minimum, ovšem s jedním zásadním detailem – celý web musí fungovat ve dvaceti jazykových mutacích.

Běžným reflexem je sáhnout po moderních frameworcích, jako je Next.js nebo Astro. Tyto nástroje jsou skvělé, ale přinášejí s sebou nezanedbatelnou režii. Znamenají stovky závislostí v package.json, složité konfigurační soubory pro bundlování a neustálou nutnost hlídat aktualizace, které mohou s každou další verzí rozbít build pipeline. Pro web, který je ve své podstatě zcela statický, mi nasazení celého SSR frameworku nepřipadalo rozumné.

Rozhodl jsem se proto jít cestou minimalismu a napsat si vlastní static-site generator v TypeScriptu. Celé řešení má zhruba šest tisíc řádků kódu, kterému plně rozumím a nad kterým mám absolutní kontrolu. Jako spouštěč a bundler mi slouží kombinace nástrojů esbuild a tsx. Celý generátor neběží jako server, ale jako jednorázový build skript v rámci CI/CD pipeline, jehož výsledkem je čistý adresář s HTML, CSS a obrázky. Ten se dá nasadit na běžný statický hosting; jediné, co k němu potřebuje navíc, je PHP pro jeden endpoint přihlášení k odběru.

Markdown a frontmatter dovnitř, HTML ven

Základní princip generátoru je přímočarý. Vstupem jsou zdrojové soubory v markdownu, které obsahují metadata ve formátu YAML frontmatter (například titulek, popis, datum publikace nebo stav překladu). Výstupem je vygenerovaná struktura HTML souborů připravená pro produkční nasazení.

Pro parsování metadat a převod markdownu do HTML stačí použít osvědčené a lehké knihovny z ekosystému npm, jako je gray-matter pro extrakci frontmatteru a markdown-it pro samotný render textu. Celý proces transformace jednoho souboru vypadá zjednodušeně takto:

import * as fs from 'fs';
import * as path from 'path';
import matter from 'gray-matter';
import MarkdownIt from 'markdown-it';

const md = new MarkdownIt({ html: true });

interface PageMetadata {
  title: string;
  description: string;
  layout: string;
}

export function renderPage(sourcePath: string, targetPath: string) {
  const fileContent = fs.readFileSync(sourcePath, 'utf-8');
  const { data, content } = matter(fileContent);
  const htmlContent = md.render(content);

  const metadata = data as PageMetadata;
  const finalHtml = applyTemplate(metadata.layout, htmlContent, metadata);

  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
  fs.writeFileSync(targetPath, finalHtml, 'utf-8');
}

function applyTemplate(layoutName: string, content: string, meta: PageMetadata): string {
  // Zde se obsah vloží do příslušné HTML šablony
  return `<!DOCTYPE html>
<html lang="cs">
<head>
  <meta name="description" content="${meta.description}">
  <title>${meta.title}</title>
</head>
<body>
  <main>${content}</main>
</body>
</html>`;
}

Tento přístup odděluje čistá data od prezentace. Šablony jsou psány jako běžné funkce v TypeScriptu vracející řetězce, takže odpadá potřeba učit se a konfigurovat specifické šablonovací jazyky typu Pug nebo Handlebars. Máme k dispozici celou sílu typového systému a moderního JavaScriptu.

Dvacet jazyků a fallback na angličtinu

Největší výzvou celého webu je lokalizace do dvaceti jazyků. Správa takového množství mutací vyžaduje robustní systém, který zabrání chybám při chybějících překladech. Ruční vytváření dvaceti verzí pro každý článek na blogu hned od prvního dne je nereálné.

Generátor proto pracuje s hierarchickým systémem lokalizace. Všechny statické texty uživatelského rozhraní jsou uloženy v překladových slovnících. Pokud pro danou stránku nebo konkrétní klíč neexistuje překlad v cílovém jazyce, automaticky použije jako fallback anglickou verzi.

Při sestavování stránek generátor nejprve projde definovaný seznam podporovaných jazyků. Pro každý jazyk se pokusí načíst příslušný markdown soubor. Jednotlivé texty rozhraní mají fallback po klíčích: chybí-li překlad konkrétního řetězce, použije se anglický originál. Celá stránka se ale takto nezastupuje — článek bez překladu se v daném jazyce prostě nevygeneruje a přepínač jazyků odvede čtenáře na domovskou stránku dané mutace, takže na 404 nenarazí.

Sitemap, hreflang a JSON-LD zadarmo

Správné SEO je pro marketingový web klíčové. U vícejazyčných webů to platí dvojnásob, protože vyhledávače vyžadují přesné mapování mezi jednotlivými jazykovými verzemi pomocí značek hreflang. Pokud by se tyto značky měly spravovat ručně, dříve či později by došlo k chybám.

Protože náš vlastní generátor má během sestavování v paměti kompletní strom všech stránek a jejich jazykových mutací, dokáže tyto metadatové úkoly vyřešit zcela automaticky. Během generování každé stránky se do hlavičky HTML vloží odkazy na všechny existující alternativní jazykové verze:

<link rel="alternate" hreflang="cs" href="https://weatherintouch.com/cs/pricing/" />
<link rel="alternate" hreflang="de" href="https://weatherintouch.com/de/pricing/" />
<link rel="alternate" hreflang="en" href="https://weatherintouch.com/en/pricing/" />
<link rel="alternate" hreflang="x-default" href="https://weatherintouch.com/en/pricing/" />

Na konci celého sestavovacího procesu generátor z nashromážděných dat vytvoří soubor sitemap.xml. Ten obsahuje všechny vygenerované URL adresy včetně vazeb xhtml:link definujících jazykové alternativy. Stejným způsobem se do stránek blogu automaticky generují strukturovaná data ve formátu JSON-LD, čímž se vyhledávačům dodá struktura článku, ze které si rich snippet mohou postavit.

Jedna pravda o cenách napříč webem i aplikací

Jedním z nejčastějších problémů u SaaS aplikací je nekonzistence dat. Ceník na marketingovém webu se často liší od reálných cen nastavených v platební bráně nebo v samotné aplikaci, protože se aktualizují nezávisle na sobě.

Při návrhu architektury celého ekosystému, o kterém píšu v článku o tom, jak je celá platforma poskládaná, jsem tento problém vyřešil sdílením kódu. Ceny, limity a parametry jednotlivých předplatných jsou definovány na jediném místě – v konstantě PLAN_CATALOG uvnitř sdíleného balíčku.

Tento balíček je importován jak do backendu aplikace, tak do našeho statického generátoru. Když se mění ceny nebo parametry tarifů, změna se provede pouze na jednom místě v kódu. Při příštím buildu webu si generátor načte aktuální data z tohoto katalogu a dynamicky vygeneruje tabulku s cenami a parametry přímo do HTML kódu ceníku. Ceny na webu se tak nemohou rozcházet déle, než trvá další build. Částky, které se skutečně strhnou, si drží platební brána zvlášť podle svých identifikátorů cen — katalog je zdrojem pravdy pro zobrazení, ne pro účtování.

Kdy bych naopak sáhl po Astro

Vlastní řešení napsané na míru má obrovské výhody v rychlosti, bezpečnosti a malé závislostní stopě. Přesto existují scénáře, kdy by bylo rozumnější zvolit hotový framework typu Astro nebo Next.js.

Vlastní generátor přestává dávat smysl v okamžiku, kdy:

  • Web vyžaduje komplexní klientskou interaktivitu (tzv. „interactive islands“), kde je potřeba kombinovat statické HTML s dynamickými komponentami v Reactu, Vue nebo Svelte.
  • Web obsahuje tisíce dynamicky se měnících stránek, kde by lineární generování v TypeScriptu trvalo neúměrně dlouho a bylo by nutné implementovat složité inkrementální sestavování.
  • Obsah webu spravuje netechnický redakční tým, který vyžaduje integraci s headless CMS a živými náhledy v reálném čase.

Pro marketingovou prezentaci s jasně definovanou strukturou a lokalizací je však vlastní minimalistický generátor tou nejefektivnější volbou. Výsledkem je statický web, jehož jediný klientský JavaScript obsluhuje souhlas s cookies, analytiku a přihlášení k odběru — a závislostí je tak málo, že se dají projít na jedno posezení.

Podrobnější pohled na to, jak tento web zapadá do celkové architektury naší předpovědní platformy, naleznete v detailu pro celý projekt Weather InTouch.

Související články

Kontaktní informace

Klidně se mi ozvěte ohledně nového projektu, konzultace nebo dlouhodobější spolupráce. Rád proberu smysluplné produktové zadání i zajímavé frontendové příležitosti.

Napište mi

© 2026 Martin Hubálek. Všechna práva vyhrazena.

Martin Hubálek IČO: 08827303 Sídlo: Dolní Štěpanice 29, 514 01 Benecko, Česká republika Zapsán v živnostenském rejstříku Nejsem plátce DPH