Menu, přesměrování, jazyky
Tři věci, které rozhodují o tvaru adres na webu. Všechny tři drží patro a vykonává je vaše šablona — proto na ně balík má hotové kusy, které stačí zapojit.
Navigace
Sekce “Navigace”Strom menu přijde ze serveru rozřešený: každá položka má hotovou adresu a položky, jejichž cíl na webu k vidění není, ze stromu vypadly i s podstromem. Nemusíte tedy počítat nic.
Hotová komponenta
Sekce “Hotová komponenta”---import Menu from "@patro/render/Menu.astro"
const menu = await client.getMenu("hlavni", locale)---
<Menu menu={menu} />null se vykreslí jako nic. Web bez navigace je pořád web; web, který kvůli
chybějícímu menu spadne, není.
Vlastní vykreslení
Sekce “Vlastní vykreslení”Když si chcete navigaci nakreslit po svém (jiné rozvržení, rozbalování, megamenu), vezměte si jen její čistou půlku:
---import { buildMenuLinks } from "@patro/render"
const menu = await client.getMenu("hlavni", locale)const links = menu === null ? [] : buildMenuLinks(menu.items)---
<ul> {links.map((link) => ( <li> <a href={link.href} class:list={["odkaz", link.classes]} {...link.attrs}> {link.label} </a> </li> ))}</ul>buildMenuLinks udělá z rozřešeného stromu položky připravené k vykreslení.
Dvě věci na tom stojí za pozornost:
attrsnese jen atributy, které položka opravdu má. Prázdná hodnota z administrace se do HTML nedostane, takže ve zdroji nesvítítitle=""a rozprostřít je jde bez podmínek.rel="noopener"se přidá samo k položkám otevíraným v novém okně.classesstojí vedleattrsschválně. Jsou to třídy, které zadal správce v administraci, a musíte je slepit se svými. Kdyby jely uvnitřattrs, rozprostření by vám přepsalo vlastníclassa odkaz by přišel o veškerý vzhled právě u těch položek, kterým správce třídu přidal.
Které menu je to hlavní
Sekce “Které menu je to hlavní”Web nemusí jméno menu znát dopředu (a je lepší, když ho neví — jinak stačí, aby se menu v administraci jmenovalo jinak, a navigace tiše zmizí). Na výběr je hotový mechanismus i s odkládací pamětí na isolate:
import { createMainMenuCache } from "@patro/render"
const cache = createMainMenuCache({ client: getPatroClient, // továrna, ne hotový klient preferred: env.PUBLIC_MENU_NAME, // volitelné přání, ne podmínka onError: (error) => console.error("patro: navigace — ", error),})
const menu = await cache.load(locale, background)Bez preferred si vybere z toho, co web má (a přeskočí prázdné menu). Vyplnit
ho má smysl teprve tehdy, když je menu víc a je potřeba říct, které je nahoře.
Paměť má smysl, protože navigace se kreslí na každé stránce — bez ní stojí
každý požadavek dvě volání navíc. background je ctx.waitUntil, kterým se
obnovení odsune mimo cestu odpovědi návštěvníkovi; na Astru ho vytáhnete
z Astro.locals pomocníkem backgroundOf.
Přesměrování
Sekce “Přesměrování”Pravidla drží patro (redaktor je zadává v administraci), ale vyhodnocuje je váš web — patro do provozu nevidí. Celý mechanismus je hotový: paměť mapy, vyhodnocení přesných shod i vzorů, hlášení nenalezených adres zpátky.
import { defineMiddleware } from "astro:middleware"import { createRedirectMiddleware } from "@patro/render"import { getPatroClient } from "./lib/patro"
export const onRequest = defineMiddleware( createRedirectMiddleware({ client: getPatroClient, onError: (error) => { console.error("patro: přesměrování — ", error) }, }),)To je celé. client je funkce, ne hotový klient — middleware vzniká jednou
při načtení modulu, kdežto env se na Cloudflare čte až za běhu požadavku.
onError je jediné místo, kde je výpadek vidět: chybu při stahování mapy ani
při odesílání hlášení middleware nepropustí do odpovědi (stránka se má
vykreslit tak jako tak), takže bez něj zmizí beze stopy.
Volitelně jdou doladit lhůty (ttlMs, maxAgeMs, flushIntervalMs); výchozí
hodnoty jsou zvolené tak, aby běžná stránka nesahala na síť vůbec.
⚠️ Vyhodnocuje se jen GET a HEAD
Sekce “⚠️ Vyhodnocuje se jen GET a HEAD”Zapamatujte si to dřív, než budete hledat, proč přesměrování „nefunguje“ na
formuláři. Přesměrovaný POST znamená ztracená data: prohlížeč po 301
i 302 požadavek zopakuje GETem, tedy bez těla.
Zápisové cesty vaší šablony (odeslání komentáře, vlastní formulář) tedy tímhle middlewarem procházejí bez zásahu, a je to správně.
Hlášení zpátky
Sekce “Hlášení zpátky”Middleware si střádá nenalezené adresy a zásahy pravidel a posílá je dávkou
z pozadí. Díky tomu vidí redaktor v administraci, na které neexistující adresy
lidé chodí, a může na ně pravidlo založit. Nemusíte kvůli tomu dělat nic —
je to součást téhož onRequest.
Jazyky
Sekce “Jazyky”Konvence je výchozí jazyk bez prefixu, ostatní s prefixem:
| Adresa | Jazyk | Slug |
|---|---|---|
/o-nas |
výchozí | o-nas |
/en/about |
en |
about |
/en |
en |
— (jazykový index) |
/cs/o-nas |
— | 404 |
Poslední řádek je vědomý: výchozí jazyk prefix nemá, takže /cs/… se jako slug
nenajde. Prefix se uzná jedině pro jazyk, který je v tenant.locales a není
výchozí.
Seznam jazyků i výchozí jazyk přijdou z client.getTenant(), takže je šablona
nemá napsané u sebe. Pravidlo samotné je hotové v @patro/shared a nepište si
ho po svém — musí totiž vyjít stejně jako adresy, které skládá server do menu:
import { entryRouteFromPath, localeHrefPrefix, entryHref } from "@patro/shared"
const tenant = await client.getTenant()
// adresa → { locale, slug }const { locale, slug } = entryRouteFromPath( Astro.params.slug ?? "", tenant.locales, tenant.defaultLocale,)
// zpátky: slug → adresaconst href = entryHref("o-nas", locale, tenant.defaultLocale)// prefix pro odkazy uvnitř jazyka ("" nebo "/en")const prefix = localeHrefPrefix(locale, tenant.defaultLocale)Slug může mít víc úseků (sluzby/uklid) a lomítka v něm jsou součástí cesty,
ne oddělovač k zakódování — proto na něj nesahejte encodeURIComponentem jako na
celek. entryHref to řeší po segmentech.
Routa
Sekce “Routa”Jeden catch-all pokryje všechno:
src/pages/index.astro → kořen ve výchozím jazycesrc/pages/[...slug].astro → /en, /en/about, /o-nas, /hledaniKořen má vlastní soubor proto, že specifičtější routa má v Astru přednost —
/ tedy do catch-allu nespadne.
Co ještě chodí z konfigurace webu
Sekce “Co ještě chodí z konfigurace webu”Vedle jazyků nese getTenant() tři nastavení, která rozhodují o adresách,
a ve slovníku adres nejsou (ten je obsahový):
| Klíč | K čemu |
|---|---|
bylineRouteSlug |
první úsek adresy autora — /autor/jan-novak |
searchRouteSlug |
první úsek adresy hledání — /hledani?q=… |
timezone |
v jakém pásmu web vypisuje časy |
Časové pásmo stojí za jednu poznámku navíc: stránka se vykresluje na serveru
a ten běží v UTC, takže bez explicitně zadaného pásma by se návštěvníkovi (a
redaktorovi v náhledu naplánované stránky) ukázal posunutý čas. Na formátování
je v @patro/shared funkce formatInTimeZone.
Titulní stránku řeší client.getFrontSlug(locale) — vrátí slug, na který má
kořen vnitřně přesměrovat (Astro.rewrite), nebo null, když titulka
nastavená není a kořen má ukázat výpis.