Skip to content

SEO uživatelského webu

Stránka vznikla jako analýza veřejné části webu (resources/views/web2, routy routes/web.php) ze dne 11. 9. 2026. Většina nálezů priority P0 je od té doby opravená balíčkem Laravel Head, zbytek je popsaný na konci.

Priorita Nález Stav
P0 Všechny stránky měly stejný titulek Chytrý kvíz Opraveno
P0 Chyběla meta description Opraveno
P0 Chyběl rel="canonical", filtry tvořily asi 900 duplicit Opraveno
P0 Chyběl Open Graph, sdílení na Facebooku bylo prázdné Opraveno
P0 Neexistuje sitemap.xml Zbývá
P1 Žádná strukturovaná data Opraveno
P1 Tři stránky nemají h1 Zbývá
P1 Chybí drobečková navigace a hreflang Zbývá
P2 Šest blokujících souborů z CDN v hlavičce Zbývá
P2 52 obrázků obchází komponentu <x-image> Zbývá

Titulek, popisek, kanonickou adresu, Open Graph i JSON-LD řeší balíček laravel/head. Šablony do hlavičky nic nepíšou - vykreslí jen direktivu @head:

  • web2/layouts/app.blade.php - veřejný web,
  • web2/layouts/guest.blade.php - přihlášení, registrace, výsledek platby,
  • web/www/layouts/base.blade.php - starý layout, po kterém jedou chybové stránky.

Hodnoty se skládají ve čtyřech vrstvách. Vyšší vrstva přepisuje nižší po jednotlivých polích, takže když routa nastaví jen titulek, popisek zůstane z výchozích hodnot.

graph LR;
    A["Výchozí hodnoty<br/>AppServiceProvider"] --> B["Metadata routy<br/>->withHead()"];
    B --> C["Běhové hodnoty<br/>Head:: v controlleru"];
    C --> D["Chybové stránky<br/>Head::errors()"];

AppServiceProvider::bootHead(). Platí pro každou stránku, která vykresluje @head:

  • titulek Chytrý kvíz – pub kvízy po celém Česku s příponou | Chytrý kvíz,
  • popisek webu,
  • canonical() z adresy requestu bez query stringu,
  • robots: all,
  • Open Graph (website, cs_CZ, název webu) a Twitter karta summary_large_image,
  • náhledovka pro sdílení z nastavení,
  • barva motivu a název aplikace.

Stránky, jejichž titulek je známý dopředu, ho mají rovnou u routy. Pro ně není potřeba sahat do controlleru:

Route::get('/pravidla', ['as' => 'rules', 'uses' => 'HomeController@showRules'])->withHead(
title: 'Pravidla kvízového večera',
description: 'Jak probíhá kvízový večer, kolik hráčů smí mít tým, jak se boduje…',
);

Stejně se dá metadata pověsit na celou skupinu rout. Takhle je z indexu vyřazená přihlášená část webu, košík i odkazy z e-mailů nad konkrétním tokenem:

Route::withHead(robots: 'noindex, nofollow')->middleware('auth')->group(function () {
// …
});

Co závisí na datech, nastaví controller přes fasádu Head. Většina těchto controllerů má na to vlastní privátní metodu describe(), aby se to nemíchalo s načítáním dat:

Head::title("Kvíz v {$pubName}, {$city} – {$schedule}")
->description(/* … */)
->og(type: OgType::Article)
->when($image !== null, fn ($head) => $head->ogImage($image, alt: $pubName));
Typ stránky Kde Poznámka
Statické stránky, e-shop, ligy routes/web.php ->withHead() u routy
Přihlášení a registrace routes/common.php podle jména routy
Detail kvízu QuizController titulek z podniku a rozvrhu, JSON-LD Event
Detail kraje RegionController titulek včetně počtu podniků
Speciál SpecialsController JSON-LD Event
Novinky BlogController JSON-LD Article, perex z obsahu
Výsledky, týmy, rezervace ResultController, TeamController, ReservationController
Produkt e-shopu EshopController JSON-LD Product s cenou a dostupností
Stránky z page builderu PageController titulek a meta_description z databáze
Hlavní stránka HomeController JSON-LD Organization
Chybové stránky AppServiceProvider Head::errors(), noindex, follow

Obrázek, který se objeví při sdílení na Facebooku nebo v Messengeru, se nastavuje v adminu v Obecná nastavení → Web → Sdílení a vyhledávače. Jsou tam dvě pole:

Pole Klíč nastavení K čemu
Náhledovka pro sdílení web.og_image og:image a twitter:image na stránkách bez vlastního obrázku
Logo do strukturovaných dat web.organization_logo logo značky v JSON-LD Organization

Prázdné pole neznamená chybějící náhled - použije se obrázek zabalený v repozitáři (konstanty DEFAULT_OG_IMAGE a DEFAULT_ORGANIZATION_LOGO v App\Settings\FrontpageSettings). Stránky, které mají vlastní obrázek (kvíz, speciál, článek, produkt), tuto náhledovku nepoužívají, spadnou na ni jen tehdy, když u nich obrázek chybí.

Typ Kde Co z toho je
Event detail kvízu (nejbližší termín), speciál panel akcí ve výsledcích vyhledávání
Article detail článku v novinkách náhled článku
Product detail produktu e-shopu cena a dostupnost ve výsledcích
Organization hlavní stránka znalostní panel značky, odkazy na sítě z nastavení

Schémata se skládají přes fasádu Schema. Balíček neprojde prázdné hodnoty, takže se do schématu nedávají pole, která model nemá vyplněná - jinak v neprodukčním prostředí vyletí výjimka a na produkci se to zaloguje jako varování.

V public/ sitemapa není, žádná routa ji negeneruje a robots.txt na ni neodkazuje. Obsah je z velké části dynamický - 526 kvízů, 1 224 podniků, 26 krajů, speciály a novinky. Bez sitemapy je Google objeví jen procházením odkazů.

Oprava: generovaná routa /sitemap.xml, ideálně s indexem a dílčími sitemapami pro kvízy, kraje, speciály a novinky. Do robots.txt přidat řádek Sitemap:. Balíček spatie/laravel-sitemap v projektu není, ale samotný XML výstup z Blade šablony je pár řádků - nová závislost není nutná.

Stránka Počet h1
/pro-podniky 0
/firemni-akce 0
/poprve-na-kvizu 0

Všechny tři skládají obsah přes web2/components/page-blocks.blade.php, který vykresluje nadpisy bloků výhradně jako h2. Stránka tak začíná rovnou druhou úrovní. Jde přitom o obchodně nejcennější stránky webu.

Oprava: page builder potřebuje u prvního nadpisového bloku volbu úrovně, nebo musí stránka nad bloky vykreslit h1 z názvu stránky.

Komponenta web2/components/map-section.blade.php má v režimu layout === 'side' nadpis <h1 id="map-heading">. Hlavní stránka tento režim nepoužívá, takže se dnes neprojeví. Jakmile ale někdo mapu použije v režimu side na stránce, která už h1 má, vzniknou dva.

Oprava: změnit nadpis mapy na h2, nebo přidat vlastnost titleTag po vzoru komponenty parallax.blade.php.

Vizuální drobečky má jen web2/pages/result-detail.blade.php. Web má přitom jasnou hierarchii Kde hrajeme → kraj → podnik → výsledky, kterou nikde nesděluje.

Oprava: sdílená komponenta drobečků a k ní Schema::breadcrumbs() v controlleru. JSON-LD je připravené, chybí jen ta komponenta.

Web má region slovensko na doméně .cz, značka hreflang nikde není. Balíček na to má metodu alternates(). Při dnešním rozsahu jde o nízkou prioritu.

Layout web2/layouts/app.blade.php na každé stránce načítá šest souborů ze tří cizích domén:

Zdroj Poznámka
Google Fonts - Hanken Grotesk, Outfit, Rubik dvě proměnná písma v plném rozsahu 100–900, blokuje vykreslení
cdnjs - Glide.js CSS (2 soubory) blokuje vykreslení i na stránkách bez karuselu
cdn.jsdelivr.net - Glide.js bez určené verze, neřízená změna cizího kódu
cdn.jsdelivr.net - GSAP 3.12.5 + ScrollTrigger načítá se i tam, kde se animace nepoužije

Oprava: písma i knihovny zabalit přes Vite a servírovat z vlastní domény, knihovny načítat jen na stránkách, které je potřebují. Verzi Glide každopádně připnout, i mimo SEO.

V web2 je 52 přímých značek <img>, které komponentu <x-image> obcházejí:

  • 7 nemá atribut alt vůbec,
  • 19 má alt="" (u dekorativních obrázků správně, u obsahových ne),
  • 3 mají loading,
  • žádná nemá width a height - přímý zdroj posunů rozvržení (CLS).

Oprava: převést obsahové obrázky na <x-image>, která rozměry, webp i odložené načítání řeší sama.

  • Adresy jsou české, čitelné a bez identifikátorů: /kviz/{slug}, /region/praha, /specialy/{slug}, /novinky/post/{slug}.
  • Přesměrování při přejmenování rout je trvalé (301). Staré adresy /blog/* a /team/list také míří na nové trvale.
  • Release stránky mají X-Robots-Tag: noindex, nofollow.
  • robots.txt blokuje SemrushBot, AhrefsBot, MJ12bot a DotBot, ostatní pouští.
  • Komponenta <x-image> umí webp, srcset, sizes, loading="lazy" i rozměry.
  • lang="cs" je na <html> v obou layoutech.
  • Formuláře chrání honeypot (ProtectAgainstSpam), newsletter má i throttle.

Testy hlavičky jsou v tests/Feature/SeoHeadTest.php, nastavení náhledovky v tests/Feature/Settings/SettingsUITest.php:

Terminal window
docker-compose exec fpm php artisan test tests/Feature/SeoHeadTest.php

Ruční kontrola libovolné stránky:

Terminal window
# Titulek, popisek, kanonická adresa a Open Graph
curl -sk -H "Host: chytrykviz.local" https://localhost/kde-hrajeme \
| grep -iE '<title>|name="description"|rel="canonical"|og:'
# Strukturovaná data
curl -sk -H "Host: chytrykviz.local" https://localhost/kviz/slavia \
| grep -o '<script type="application/ld+json">[^<]*'
# Počet nadpisů h1 na stránce (musí být přesně 1)
curl -sk -H "Host: chytrykviz.local" https://localhost/pro-podniky \
| grep -o '<h1' | wc -l

Po nasazení zkontrolovat v Google Search Console sestavu Stránky (pokles duplicit), Vylepšení → Akce (strukturovaná data kvízů) a Core Web Vitals.