Titulní obrázek

Jak převést Wordpress web na statické stránky pomocí Eleventy

Mnoho let jsem používal pro blogování Wordpress. Přes jeho výhody mě vadily nároky spojené s udržováním webu. Párkrát se mi dokonce stalo, že byl můj web kvůli bezpečnostní díře v některém z pluginu napaden a já to musel složitě řešit. Do toho není web na Wordpressu nejrychlejší, pokud tedy nevěnujete hodně péče optimalizaci. V kombinaci s tím, že svůj web jsem neaktualizoval moc často, došel jsem k závěru, že přejdu na statický web, který bude generován nějakým generátorem statických stránek. Z velkého výběru řešení to u mě vyhrálo Eleventy.

Upozornění: Návod je vhodný pro technicky více zdatné uživatele.

Tip: Na konci článku najdete odkaz na Github repozitář, kde najdete nakonfigurovaný Eleventy se šablonami, stejný jako používám na tomto webu.

Rozdíly mezi Wordpressem a Eleventy

Web postavený na Wordpressu je dynamický. Jde o hromadu php skriptů, které ve chvíli kdy uživatel zadá nějaké url, vytáhne z databáze obsah, sestaví HTML stránku a vrátí ji uživateli (zjednodušeně řečeno). Pokud není aktivní nějaká forma cachingu, stránka se generuje při každém requestu a to nějaký čas zabere.

Naproti tomu v případě Eleventy (a ostatních generátorů statických stránek) probíhá vygenerování výsledné HTML stránky jednou, v době buildu. Stránka se vygeneruje, uloží a pak už se servíruje uživatelům, kteří zadají url do prohlížeče. Hostovat statický web je jednoduché a často to nic nestojí. A je to velmi rychlé. A ušetří to i práci s údržbou, protože pokud na webu nechcete něco měnit, nemusíte nic dělat.

U Wordpressu můžete obsah snadno vytvářet pomocí admin rozhraní, kde ve WYSIWYG editoru píšete obsah, vkládáte fotky a případně můžete web a pluginy nastavovat. Naproti tomu pro Eleventy takové možnosti nemáte. Ten pracuje se soubory v adresáři projektu. Články píšete v obyčejných textových souborech a k formátování používáte markdown syntaxi.

Mé požadavky na nový web

  1. Obsah uložen v běžném markdown formátu tak, aby byl použitelný sám o sobě a snadno přenesitelný v případě, že budu chtít od Eleventy odejít.
  2. Jednoduché přidávání nového obsahu.
  3. Automatická optimalizace obrázků pro zrychlení načítání stránek.
  4. Možnost jednoduše vytvářet galerie fotek bez nutnosti složitého nastavování - jen vytvořit adresář s fotkama a do textu vložit název galerie.

Plán migrace

Pokud se rozhodnete převést web, je potřeba si rozmyslet, co vše chcete z webu přenést. Pro mě byly hlavní blogové příspěvky. Čím více speciálních fíčur jste ve Wordpressu používali (různé pluginy, galerie, formátování), tím náročnější migrace bude. Tady doporučuju si sepsat seznam, podle kterého pak pojedete, ať na nic nezapomenete.

Postup krok za krokem

Napíšu tady obecně postup, ten váš se může lišit podle toho co od nového webu čekáte.

Převedení článků do markdown formátu

  1. Přihlašte se do admin rozhraní WP webu. Z levého menu vyberte Nástroje -> Export. Tam vyberte Všechen obsah a stáhněte si data do počítače. Výsledek bude jeden xml soubor.
  2. Nainstalujte si tool wordpress-export-to-markdown (potřebujete mít nainstalovaný Node.js) podle návodu v README projektu
  3. Spusťte nástroj pomocí npx wordpress-export-to-markdown a průvodce vás provede možnostmi konverze. Pokud zvolíte i stahování obrázku, budou se postupně stahovat. TIP: několikrát mi stahování obrázků selhalo a ne všechny se stáhly. Musel jsem to pustit několikrát, abych dostal všechny.
  4. Hromadně (např. v nějakém textovém editoru) nahraďte absolutní url na články a obrázky v rámci webu relativními (takže místo https://nogol.cz/tag/projekty/ -> /tag/projekty/). Díky relativním cestám budou odkazy fungovat i na localhostu.
  5. Ručně projděte jednotlivé markdown soubory a dolaďte formátování a případné problémy.

Extra tip: pokud chcete převést i komentáře, musíte si je z exportovaného xml souboru vytáhnout a přidat do existujících souborů. Já si nechal takový skript vytvořit od ChatGPT, stačilo pár iterací a dělalo to co potřebuju - vytáhne to komentáře, seřadí podle času a vloží je do správných příspěvků. Ke spuštění skriptu potřebujete ještě nainstalovat xml2js pomocí npm install xml2js. Ve skriptu níže pak stačí upravit cestu, případně si doladit strukturu v jaké máte příspěvky uložené.

const fs = require('fs');
const path = require('path');
const xml2js = require('xml2js');

// Cesta k tvému exportu z WordPressu
const WORDPRESS_EXPORT_PATH = '/home/davidnogol/personal/wordpress_to_md/WordPress.2024-12-18.xml';

// Cesta k adresáři s Markdown soubory příspěvků
const POSTS_DIR = path.join(__dirname, 'post');

// Funkce pro převedení data na čitelný formát
function formatDate(dateStr) {
    const date = new Date(dateStr);
    return date.toLocaleString('cs-CZ', { timeZone: 'Europe/Prague' });
}

let commentCount = 0;

// Načtení a parsování XML souboru
console.log('Čtu XML soubor...');
fs.readFile(WORDPRESS_EXPORT_PATH, (err, data) => {
    if (err) {
        console.error('Chyba při čtení souboru:', err);
        return;
    }

    console.log('Parsování XML...');
    xml2js.parseString(data, { trim: true }, (err, result) => {
        if (err) {
            console.error('Chyba při parsování XML:', err);
            return;
        }

        const items = result.rss.channel[0].item;
        if (!items || items.length === 0) {
            console.log('Žádné příspěvky nebyly nalezeny v exportu.');
            return;
        }

        console.log(`Nalezeno ${items.length} příspěvků.`);
        // let mdContent = ''; // Není potřeba pro jednotlivé soubory

        items.forEach((item, index) => {
            const title = item.title ? item.title[0] : 'Bez názvu';
            const postLink = item.link ? item.link[0] : '';
            const comments = item['wp:comment'] ? item['wp:comment'] : [];

            // Získání post_name a post_date pro určení cesty k index.md
            const postName = item['wp:post_name'] ? item['wp:post_name'][0] : null;
            const postDateStr = item['wp:post_date'] ? item['wp:post_date'][0] : null;

            if (!postName || !postDateStr) {
                console.warn(`Příspěvek "${title}" nemá vyplněný wp:post_name nebo wp:post_date. Přeskakuji.`);
                return;
            }

            // Parsování data pro získání roku a měsíce
            const postDate = new Date(postDateStr);
            if (isNaN(postDate)) {
                console.warn(`Příspěvek "${title}" má neplatné datum: ${postDateStr}. Přeskakuji.`);
                return;
            }
            const year = postDate.getFullYear().toString();
            const month = String(postDate.getMonth() + 1).padStart(2, '0'); // Měsíc od 1 do 12

            // Konstrukce cesty k index.md
            const indexPath = path.join(POSTS_DIR, year, month, postName, 'index.md');

            // Kontrola existence index.md
            if (!fs.existsSync(indexPath)) {
                console.warn(`Soubor ${indexPath} neexistuje pro příspěvek "${title}". Přeskakuji.`);
                return;
            }

            console.log(`Příspěvek ${index + 1}: "${title}" - nalezeno ${comments.length} komentářů.`);

            if (comments.length > 0) {
                // Seřazení komentářů podle data (od nejstaršího po nejnovější)
                comments.sort((a, b) => {
                    const dateA = new Date(a['wp:comment_date'][0]);
                    const dateB = new Date(b['wp:comment_date'][0]);
                    return dateA - dateB; // Od nejstaršího po nejnovější
                });

                // Vytvoření sekce komentářů
                let commentsMarkdown = '\n\n## Komentáře\n\n';

                comments.forEach(comment => {
                    const commentAuthor = comment['wp:comment_author'] ? comment['wp:comment_author'][0] : 'Neznámý autor';
                    const commentAuthorURL = comment['wp:comment_author_url'] ? comment['wp:comment_author_url'][0] : null;
                    const commentDateStr = comment['wp:comment_date'] ? comment['wp:comment_date'][0] : '';
                    const commentDate = commentDateStr ? formatDate(commentDateStr) : 'Neznámé datum';
                    const commentContent = comment['wp:comment_content'] ? comment['wp:comment_content'][0] : '';

                    // Filtrování prázdných nebo nežádoucích komentářů
                    if (!commentContent || commentContent.startsWith('[…]') || commentContent.startsWith('[...]')) {
                        return;
                    }

                    // Formátování autora s odkazem, pokud URL existuje
                    let formattedAuthor;
                    if (commentAuthorURL && commentAuthorURL.trim() !== '') {
                        formattedAuthor = `[${commentAuthor}](${commentAuthorURL})`;
                    } else {
                        formattedAuthor = commentAuthor;
                    }

                    commentsMarkdown += `### ${formattedAuthor}\nDatum: ${commentDate}\n${commentContent.replace(/\r?\n/g, '  \n')}\n\n`;
                });

                // Zapsání komentářů do index.md
                fs.appendFile(indexPath, commentsMarkdown, (err) => {
                    if (err) {
                        console.error(`Chyba při zápisu do souboru ${indexPath}:`, err);
                        return;
                    }
                    console.log(`Komentáře byly úspěšně přidány do souboru ${indexPath}`);
                });

                commentCount += comments.length;
            }
        });

        if (commentCount === 0) {
            console.log('Žádné komentáře nebyly nalezeny.');
            return;
        }
    });
});

Pokud chcete zachovat stejné URL jako jste měli v původním webu, můžete:

  1. Uložit soubory do adresářové struktury tak, aby odpovídali cestě v url. Takže např. článek https://nogol.cz/2025/01/nova-verze-blogu-a-odchod-od-wordpressu/ bude v adresáři src/2025/01/nova-verze-blogu-a-odchod-od-wordpressu/ a tam bude soubor index.md.
  2. Nastavte si to v YAML frontmatter (vysvětlím níže) pod proměnnou permalink.

Nakonfigurujte si .eleventy.js

Tento soubor slouží pro konfiguraci, jak a kam se má web generovat, jak se mají transformovat obrázky, případně jaké pluginy se mají použít. Vycházejte z různých examplů co najdete, třeba z toho mého uvedeného na konci článku.

Vytvořte si šablonu/šablony

Funguje to tak, že v každém .md souboru máte na začátku dokumentu (mezi dvojicí --- řetězců) metadata, které se použijou při generování HTML stránky. Eleventy podporuje formát YAML, json a js. Tam si můžete definovat hodnoty různých proměnných. Jedním z nich je parametr template. V něm nastavíte název šablony, která se použije pro vygenerování stránky.

Například začátek souboru s tímto článkem vypadá takto:

---
title: "Jak převést Wordpress web na statické stránky pomocí Eleventy"
date: "2025-01-16"
type: "blogpost"
tags:
  - "Programování"
layout: post-detail.njk
---

Můžete použít sérii šablon, které se poskládají v jednu stránku. Například zvlášť šablonu pro celou stránku, pak další šablonu která z ni bude vycházet a bude zobrazovat detail článku. Další bude zobrazovat výpis článků atd. Pro pochopení koukněte do kodu v odkazu na konci článku, tam je vidět jak je poskládatný tento web.

Eleventy nabízí mnoho různých formátů, ve kterých můžete šablony psát, já zvolil nunjucks formát.

Web si otestujte lokálně

Příkazem npx @11ty/eleventy --serve pustíte build webu, vygenerují se statické stránky do vámi nastaveného adresáře a spustí se lokální server, kde si můžete výsledné stránky prohlížet v prohlížeči na adrese http://localhost:8080/. Výhoda je, že pokud upravíte nějaký ze souborů, web se probuildí a vy tak v prohlížeči vidíte aktuální hodnotu (pozor, změny css souborů nevedou k aktualizaci, musíte pak běžící proces zastavit a znovu to nastartovat).

Publikace webu

Ruční varianta

Máte několik možností. Nejobyčejnější varianta je, že příkazem npx @11ty/eleventy vygenerujete celý web do jednoho adresáře. Ten pak můžete nahrát přes FTP do adresáře u vašeho hostingu. A máte hotovo - pokud jde o web kde nebudete dělat moc změn, celý ten proces vás čeká při každé změně web. Proto jsem tuto možnost zavrhl, nechtělo by se mi to při každé úpravě webu řešit.

Automatizovaná varianta

Hledal jsem pohodlnější způsob, jak web aktualizovat. Celý projekt mám uložený na Githubu v repozitáři. K hostování jsem zvolil Netlify, kde nabízí pro tyto malé projekty dost velkorysý free tarif.

Při přidávání Netlify site pak zvolíte Import from existing project -> Github. Vyberete si váš repozitář s projektem, udělíte Netlify oprávnění a pak už nastavíte kterou větvi má Netlify hlídat na změny.

Když pak chcete přidat nový článek, nebo stávající editovat, stačí pak změny pushnout do zvolené branche a Netlify změny detekuje, spustí build a do pár minut máte web aktuální.

BONUS: Ukázkový repozitář

Pokud si to chcete vyzkoušet, připravil jsem pro vás ukázkový repozitář, na kterém si můžete vyzkoušet jak se s takovým webem pracuje. Pokud ho použijete, budete mít. Stačí repozitář naklonovat, spustit npm start a prohlédnout si, jak vypadá výsledek. Rovnou si můžete s webem pohrát, přidávat nové stránky. A třeba si ho přizpůsobit pro své účely a rovnou na něm postavit vlastní web :)

Repozitář najdete zde: https://github.com/davidnogol/eleventy_blog_example

Rád si poslechnu váš názor! Pokud chcete přidat komentář k tomuto článku, pošlete mi ho na . Komentáře, které obohatí článek o zajímavé informace nebo podnětné otázky, budou ručně přidány s odkazem na váš web.