Proč jsem si na marketingový web napsal vlastní SSG místo Astro
Pro třístránkový web ve 20 jazycích je Astro nebo Next overkill. Pár set řádků esbuildu a tsx udělá totéž — a rozumím každému řádku.

Kdy je plný framework overkill
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, 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.
V dnešní době je běžným reflexem sáhnout po moderních frameworcích jako 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í robustního server-side rendering (SSR) frameworku nedávalo smysl.
Rozhodl jsem se proto jít cestou minimalismu a napsat si vlastní static-site generator v TypeScriptu. Celé řešení má pouhých několik set řádků kódu, kterému plně rozumím a mám nad ní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, které lze okamžitě nasadit na jakýkoli statický hosting nebo CDN.
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 lze zjednodušeně ilustrovat následujícím způsobem:
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é TypeScript funkce vracející řetězce, což eliminuje potřebu učit se a konfigurovat specifické šablonovací jazyky typu Pug nebo Handlebars. Máme k dispozici plnou 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 generátor narazí na situaci, kdy 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. Pokud soubor v dané jazykové mutaci neexistuje, načte se anglický originál, ale vygeneruje se na správné URL adrese odpovídající cílovému jazyku. Tím je zajištěno, že uživatel nikdy nenarazí na nefunkční odkaz nebo chybu 404, i když překlad konkrétního článku ještě nebyl dokončen.
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.
Vzhledem k tomu, ž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/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, což vyhledávačům usnadňuje indexaci článků a zlepšuje jejich zobrazení ve výsledcích vyhledávání.
Jedna pravda o cenách napříč webem i appkou
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 obě místa 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 interním TypeScript balíčku PLAN_CATALOG.
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 tak nikdy nedriftují oproti realitě v aplikaci.
Kdy bych naopak sáhl po Astro
Vlastní řešení napsané na míru má obrovské výhody v rychlosti, bezpečnosti a nulové režii na údržbu. 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 bezhlavým CMS (Headless CMS) s ž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 extrémně rychlý web bez zbytečného JavaScriptu, který nevyžaduje žádnou údržbu závislostí.
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.

