zápisok · weby · ~7 min čítania
Ako vznikol web OZ Juliána: redizajn bez WordPressu a s vlastnou správou aktualít
Prečo web malého združenia beží bez WordPressu a databázy, ako funguje správa aktualít s bezpečným prihlásením z telefónu a ako sa overoval redizajn.
V administrácii hostingu je tlačidlo „nastaviť DNS“. Kto naň klikne, ticho prepíše záznamy, podľa ktorých prehliadač hľadá server domény: zrazu mieria priamo na server hostingu, mimo CDN (siete serverov, ktorá web doručuje a chráni). Web beží ďalej, takže si zmenu nikto nevšimne. Na ozjuliana.sk sa presne toto stalo a zmenu neodhalil človek, ale skript, ktorý po každom nasadení živý web iba číta. Takých poistiek má web malého združenia viac, než by sa čakalo, a tento zápisok je o tom, prečo tam sú.
Občianske združenie Juliána celoročne zbiera oblečenie, obuv, hračky a potreby do domácnosti a rozváža ich ľuďom v núdzi. Web ozjuliana.sk preň dobrovoľnícky robím a spravujem od apríla 2026. Zadanie znie jednoducho: povedať, čo združenie robí a ako sa dá pomôcť, a umožniť človeku bez technického zázemia pridať z telefónu správu o zbierke aj s fotkami. Čísla v skratke sú v prípadovej štúdii; tu je dlhší príbeh.
Prečo nie WordPress a prečo súbory JSON bez databázy
Web beží na lacnom zdieľanom webhostingu za CDN. Na serveri je iba PHP: žiadne SSH (vzdialený príkazový riadok), žiadny Git, nasadzuje sa cez FTPS (prenos súborov šifrovaným spojením). WordPress by tam bežal, ale priniesol by so sebou pravidelné aktualizácie jadra a pluginov, databázu a nekonečné pokusy o prihlásenie na všeobecne známej adrese. Združenie pritom pridá článok s fotkami raz za pár týždňov. Pre takú potrebu sa neoplatí udržiavať celý redakčný systém.
Výsledkom je vlastné PHP bez frameworku a bez Composera. Verejné stránky sú šablóny, ktoré fungujú aj bez JavaScriptu. Tri malé skripty len pridávajú animácie, lištu so súhlasom, úvodnú clonu s logom a prehliadač fotiek.
Databáza nie je ani pre články. Každý článok je jeden súbor JSON (textový formát pre štruktúrované údaje), fotky ležia vedľa v priečinku a pri nahratí ich zmenší knižnica GD. Aj SQLite by sa dala použiť, lenže pri desiatkach článkov nič nezrýchli a súbory majú dve praktické výhody: celý obsah webu sa zálohuje skopírovaním dvoch priečinkov a v núdzi sa dá článok opraviť cez FTP v obyčajnom textovom editore.
Zápis ide vždy do dočasného súboru, ktorý sa až potom premenuje na cieľový, takže prerušené uloženie nenechá napoly zapísaný článok.
{
"slug": "zbierka-satstva-2026",
"title": "Zbierka šatstva 2026",
"status": "published",
"published_at": "2026-09-18T12:00:00+02:00",
"text": "Ďakujeme všetkým, ktorí prispeli.\n\nZbierka pokračuje každý piatok.",
"cover": { "big": "uploads/…-big.jpg", "thumb": "uploads/…-thumb.jpg", "alt": "Triedenie darovaného oblečenia" },
"gallery": []
}
Článok je vymyslený, skutočné súbory majú navyše čas vytvorenia a poslednej úpravy. Obsah zo servera každý večer stiahne záloha, ktorá cez FTPS iba číta a drží 30 denných snímok. Pri chybe príde e‑mail.
Dvojfaktorové prihlásenie v čistom PHP
Administrácia článkov je jediné miesto, kde sa dá obsah webu zmeniť, preto má povinné dvojfaktorové prihlásenie: heslo a šesťmiestny kód z aplikácie Google Authenticator. Bez druhého faktora účet ani nevznikne.
TOTP, teda jednorazový kód odvodený od času (norma RFC 6238), je prekvapivo malý algoritmus: HMAC (kryptografický odtlačok) z tajného kľúča a poradového čísla 30-sekundového okna, z výsledku sa vyberie šesť číslic. Na hostingu bez Composera je jednoduchšie napísať ho v čistom PHP zo štandardných funkcií, než ručne nahrávať knižnicu so závislosťami. Implementácia sa overuje proti testovacím hodnotám priamo z normy a testovacia sada bez závislostí beží pred každým nasadením.
Okolo toho je bežná výbava: heslá uložené ako odtlačok bcrypt, ochranný token proti podvrhnutému odoslaniu (CSRF) v každom formulári, session s obmedzenou platnosťou, počítadlo neúspešných pokusov a prvý účet, ktorý sa nedá založiť bez prístupu k serveru. Konkrétne nastavenia tu zámerne nie sú; útočníkovi by pomohli viac než čitateľovi.
Administrácia pre telefón a človeka bez technického zázemia
Obsah sa spravuje takmer výlučne z telefónu. Administrácia je preto navrhnutá od šírky 320 px: väčšie tlačidlá a zaškrtávacie polia, dotykové ciele aspoň 44 px a vstupné polia s písmom aspoň 16 px, aby iPhone pri ťuknutí stránku nepribližoval. Funguje bez JavaScriptu, takže ju nerozbije ani starší prehliadač.
Formulár má len to, čo treba: nadpis, dátum, text, úvodnú fotku, galériu, popisy fotiek a prepínač medzi konceptom a zverejnením. Text je obyčajný text, prázdny riadok znamená nový odsek. Žiadny editor s tlačidlami, ktorý by do článku vkladal skryté HTML. Fotky z telefónu sa otočia podľa údajov EXIF, ktoré do nich zapíše fotoaparát, zmenšia a uložia bez metadát. Pri audite sa overilo všetkých 84 fotiek na živom webe a ani jedna nemá GPS súradnice.
Jedno rozhodnutie vzišlo priamo z toho, kto web obsluhuje. Prehliadač, v ktorom sa už niekto úspešne prihlásil, má vlastné počítadlo pokusov. Cudzie pokusy o prihlásenie tak môžu zablokovať neznáme zariadenia, nie však telefón, z ktorého sa obsah naozaj pridáva.
K administrácii patrí aj krátky návod v slovenčine. Najužitočnejšia rada v ňom nie je technická: prvá veta článku má povedať kedy, kde, čo a koľko, lebo z nej vzniká úryvok na kartách aj popis pre vyhľadávače.
Lišta so súhlasom podľa zákona 452/2021
Zákon o elektronických komunikáciách v § 109 ods. 8 vyžaduje súhlas s ukladaním údajov v zariadení návštevníka, pokiaľ nie sú nevyhnutné na fungovanie služby. Web je postavený tak, aby bez súhlasu nepotreboval nič také: verejné stránky nenastavujú žiadnu cookie ani nespúšťajú session a písma sa načítavajú z vlastného servera, nie z Google Fonts.
Google Analytics sa načíta až po kliknutí na „Prijať“. „Prijať“ a „Odmietnuť“ sú na prvej vrstve rovnocenné tlačidlá, voľba sa dá kedykoľvek zmeniť odkazom v pätičke a odvolanie zmaže analytické cookies. Každá voľba sa zapíše na server ako doklad o súhlase: náhodný identifikátor, čas, verzia a voľba, bez IP adresy. Výnimkou je vložená mapa Google v kontakte, ktorá sa načíta hneď; stránka o ochrane osobných údajov to otvorene uvádza.
Redizajn, ktorý sa dá zmerať
V septembri prišiel redizajn s pracovným názvom „Pokojná elegancia“. Predchádzal mu audit starej verzie a výsledky boli triezve: 29 rôznych veľkostí písma, hranice vstupných polí s kontrastom 1,24 : 1, štyri nálezy nástroja axe na automatickú kontrolu prístupnosti a navigácia, ktorá na niektorých šírkach telefónu o pár pixelov pretekala.
Práca mala dve fázy. Prvá bola refaktoring s jediným pravidlom: žiadna viditeľná zmena. Spoločná šablóna, jeden modul kontaktného formulára, farby a veľkosti ako premenné.
Po každom commite sa nová verzia porovnala so starou: HTML výstupy 41 scenárov bajt po bajte na PHP 8.4 aj 8.5 a screenshoty pixel po pixeli. Na konci fázy boli výstupy zhodné, okrem jednej vopred schválenej opravy, a 450 screenshotov (15 stránok × 10 šírok × 3 režimy: bežný, bez JavaScriptu a so zníženým pohybom) malo rozdiel 0 pixelov. Celá sada bežala bez administrátorských práv: PHP vo WebAssembly a Chrome bez okna, ovládaný cez Playwright.
Až potom nasledoval samotný redizajn. Každá zamýšľaná odchýlka sa zapísala vopred a porovnanie ju normalizovalo; všetko ostatné muselo ostať zhodné. Takto sa nedá omylom zmeniť stránka, ktorá sa meniť nemala.
Výsledok: typografická škála s 12 krokmi (audit pred redizajnom napočítal 29 hodnôt), hranice polí s kontrastom nad 3 : 1, nula porušení podľa axe (pravidlá prístupnosti WCAG 2, úrovne A a AA) v 30 behoch na 10 stránkach a žiadne vodorovné pretečenie v rozsahu 320–1440 px, v najrizikovejších pásmach kontrolované po jednom pixeli. Sekcia o poslaní združenia sa na mobile skrátila z približne 1 000 na 450 px a logo v hlavičke telefónu narástlo z 20 × 20 na 36 × 32 px, takže je konečne čitateľné.
Čo sa pokazilo a ako sa to opravilo
Audit pred redizajnom našiel v pôvodnej administrácii chyby, ktoré bežné klikanie neodhalí. Apostrof v nadpise rozbil potvrdzovacie okno pri mazaní, takže článok sa zmazal bez opýtania. Neplatný znak UTF-8 v mene účtu viedol k zápisu prázdneho súboru s účtami. Na PHP 8.5 navyše zastaraná funkcia na prácu s obrázkami vypísala po uložení článku varovanie a presmerovanie sa nevykonalo. Všetky tri chyby boli opravené a overené ešte pred spustením.
Hneď po spustení prišiel bezpečnostný audit živého webu v dvoch kolách, druhé z nich s dvoma nezávislými útočnými revíziami kódu. Priniesol 38 zistení, ale cestu do administrácie bez prihlásenia nenašlo ani jedno kolo. Najpoučnejšie zistenie: zo 16 súbežných pokusov so zlým heslom sa nezablokoval ani jeden, lebo pokus sa započítal až po kontrole hesla. Dnes sa pokus započíta pod zámkom ešte pred ňou. Opravy kódu išli v piatich pull requestoch v ten istý deň.
Posledná chyba, tá s tlačidlom z úvodu, nebola v kóde. Doména po jednom kliknutí mierila priamo na server hostingu a web fungoval ďalej, len bez CDN a jej ochrany. Prezradil to skript, ktorý po každom nasadení kontroluje živý web iba čítaním: neprešla mu kontrola certifikátu. Záznamy sa vrátili hneď nasledujúce ráno. Bez toho skriptu mohol web takto bežať celé mesiace a nikto by si to nevšimol.
Čo si z toho beriem
- Malé združenie nepotrebuje redakčný systém, potrebuje jeden dobrý formulár. Pri desiatkach článkov mu stačia aj súbory bez databázy, ak je zápis atomický a záloha automatická.
- Návrh administrácie začína otázkou, kto ju obsluhuje a na akom zariadení. Od toho sa odvíja aj bezpečnosť, napríklad dôveryhodný prehliadač s vlastným počítadlom.
- Refaktoring bez merania stojí na dojme. Bajtová a pixelová parita mení vetu „myslím, že sa nič nezmenilo“ na dôkaz.
- Audit sa oplatí dvakrát: na starom kóde pred zmenami a na živom webe po spustení. Každý z nich našiel niečo iné.
- Kontrola nasadenia, ktorá živý web iba číta, chytí aj chyby mimo kódu.
Tlačidlo „nastaviť DNS“ je v administrácii hostingu dodnes. Skript, ktorý na to pri najbližšom nasadení príde, tiež.