Routování adres: ID vs. slug vs. jejich kombinace

koren
Člen | 59
+
0
-

Budeme brzy spouštět větší projekt s 15K+ články a tak hodně řeším, v jakém tvaru budou URL. Technicky to vyřešit umím, ale spíš mi jde o rychlost routování a nějaké best practice pro projekt podobné velikosti.

Možné tvary URL jsou např. tyto a já řeším, který z nich vybrat:

  1. domena.cz/clanek/21
  2. domena.cz/clanek/21-nette-je-super
  3. domena.cz/clanek/nette-je-super

S ohledem na SEO by mi přišlo vhodné, aby URL byla generováno z titulku. A protože mám rád věci čisté, intuitivně bych se přikláněl nejvíc k té poslední variantě. Zároveň ale vím, že jakmile tam budu mít ten slug, budu to muset řešit pomocí FILTER_IN/OUT v návaznosti na uložený záznam v DB. Bojím se ale, jestli tahle cesta to routování nějak moc nezpomalí. Určitě se to dá vylepšit tím, že danému sloupci dám v DB index, ale i tak nevím, jestli to celé není nějaké zásadní zpomalení oproti variantě, kdybych routoval jen pomocí ID (tj. první tvar URL výše)… Nebo když se ty routy cachují, tak je to v zásadě jedno? :)

Jde mi o to, že u větších portálů vídám, že ID je často součástí URL (typ 2 výše). A vždycky mě zajímalo, jestli to má i nějaký technický důvod, anebo je to jen pro snazší identifikaci daného článku pro někoho z redakce. Je jasné, že když se použije tvar 3, tak se pak musí podchytit ještě to, aby v systému neexistovaly duplikáty slugů, ale to nevnímám jako problém zařídit…

Takže jaký tvar URL preferujete vy a proč? ;)

nightfish
Člen | 472
+
+8
-

@koren IMHO pro routování ve variantě 3 žádný FILTER_IN ani FILTER_OUT nepotřebuješ – to, že máš routovat na ArticlePresenter poznáš podle části /clanek/ – presenter pak dostane $slug jako argument, ověří si, že v DB článek existuje – pokud ne, vyhodí 404. Vytváření odkazů je pak jenom {link Article:default $slug}, takže taktéž triviální.

Problém by byl, kdybys URL nechtěl mít /clanek/ – to bys pak muset při každém request kontrolovat, jestli existuje nějaká článek podle slugu a pokud ano, zaroutovat na něj a v opačném případě se pokusit routovat někam jinam (např. na produkty atd.).

Kcko
Člen | 465
+
+1
-

Varianty 1 a 2 jsou snažší o to, že Ti nevznikne duplicitní URL, kterou budeš muset systémově vyřešit.

Dnes bude horko (takový článek vznikne každý rok v létě), takže jaká bude url?

dnes-bude-horko
dnes-bude-horko-1
dnes-bude-horko-<datum>
dnes-bude-horko-<random-string>

A varianta 3 je jak psal @nightfish a není na tom nic složitého, ale opět si musíš vyřešit možnou kolizi.

froggy
Člen | 17
+
+4
-

Doplním, že u varianty 2 a 3 je potřeba vyřešit správný redirect po přejmenování slugu pro případ, že se slug bere vždy z aktuálního názvu článku – 301 moved permanently. U varianty 2 je to jednodušší, u varianty 3 je třeba uchovávat tabulku se všemi přejmenovanými slugy.

koren
Člen | 59
+
0
-

Ok, díky všem za odpovědi! Nějak jsem vždy všechno řešil přes ID, protože mi to pak přijde takové jistější (ID je prostě ID). Takže všechny action metody i odkazy v šablonách už mám nyní udělané tak, že to loaduje články přes ID (prostě klasika public function actionItem(int $id): void {}).

Ta cesta navržená od @nightfish je taky dobrá (mám to jako /clanky/, takže ok), ale kdybych chtěl mít slug jako hlavní identifikátor, musel bych teď přepsat podstatnou část aplikace… I toho důvodu bych se možná přikláněl k tomu pracovat s tím ID a slug na to mapovat až přes ten FILTER_IN/OUT (nebo i jinak, pokud by šlo)… Mě osobně to tak řešit nevadí, ale pořád vlastně nevím, jestli to nějak neovlivňuje tu rychlost…

Každopádně když už jde moje uvažování tímhle směrem, nakonec přijde nejpraktičtější, aby to fungovaly obě varianty 1 a 2, s tím, že 2 by byla kanonická. Jak byste v takovém případě řešili tvorbu odkazů? Je na to třeba použít ten FILTER_IN/OUT nebo by se na to dalo jít ještě nějak jinak?

Pavel Kravčík
Člen | 1180
+
+1
-

Káčko to naznačil, poslední dobou se mi nejvíce líbí:

Router /clanek/<id>[/<slug>]:Front:Article:detail a v presenteru se rozhodneš, jestli je slug povinný nebo ne.

Presenter public function actionDetail(int $id, ?string $slug) A článek hledáš jen dle ID, ale pro uživatele to vypadá docela hezky… teď jsem si všiml, že takhle to řeší Nette fórum. :) Tak asi není potřeba vysvětlovat víc, vyzkoušej si sám třeba ten slug změnit. Nebo změnit ID.

Kcko
Člen | 465
+
+2
-

Pavel Kravčík napsal(a):

Káčko to naznačil, poslední dobou se mi nejvíce líbí:

Router /clanek/<id>[/<slug>]:Front:Article:detail a v presenteru se rozhodneš, jestli je slug povinný nebo ne.

Presenter public function actionDetail(int $id, ?string $slug) A článek hledáš jen dle ID, ale pro uživatele to vypadá docela hezky… teď jsem si všiml, že takhle to řeší Nette fórum. :) Tak asi není potřeba vysvětlovat víc, vyzkoušej si sám třeba ten slug změnit. Nebo změnit ID.

Tady bych to jen upravil, aby slug byl povinný a nemusí být zanesený ani v DB, může se generovat „on the fly“. Hledá se jen podle ID, slug tam je jen jako pozlátko pro hezčí URL a v detail článku, by se ale slug zkontrolovat měl, aby pak nevniklo to, co kdysi bylo na novinky.cz (nebo na nějakém takovém portálu).

/clanek/23543-dnes-bude-horko
=> /clanek/23543-ty-si-blb

a článek pořád fungoval ;-)

Michalek
Člen | 210
+
+3
-

U nás (260k článků) máme taky /clanek/123-titulek-dlouhy-delsi/, včetně krásné zkratky /c/123 kterou používáme při tiskové verzi, nebo posílání odkazů a nemusíme zkracovat přes třetí služby :-)

Filter na routeru nepoužívám, jen při každém zobrazení článku zkontroluju, zda id-slug odpovídá aktuální variantě. Když ne, přesměruju. Řeší to i různé verze článku když se změní titulek (/clanek/123-stary-titulek se přesměruje automaticky na /clanek/123-titulek-dlouhy-delsi/ (nemusí se držet historie změn jako s URL bez ID, prostě se to zkontroluje vůči databázi podle ID)

Editoval Michalek (19. 8. 2022 13:28)

Marek Znojil
Člen | 76
+
0
-

Slug nemusí být ani jako parametr v action metodě. Stačí na null parametr v routě aplikovat filtr a dle id dosadit do parametrů požadovaný slug. Pole se slugy bych ještě určitě uložil do keše aby se při tvorbě každého url nemuselo lozit do databáze.

m.brecher
Generous Backer | 735
+
0
-

Já to řeším tak, že pro články používám url ve tvaru /article/nejaky-titulek-clanku

slug /nejaky-titulek-clanku se odvodí z title článku (Nette\Utils\Strings::webalize()) .

Pole slug v tabulce article má nastaven unikátní klíč. Před uložením slugu nového/editovaného článku kontroluji zda již existuje a pokud ano, tak přidám číslo verze /article/nejaky-titulek-clanku-verze-1 a opakuji test zda url existuje, až se najde volné.

Článek se nevyhledá podle id, ale podle slug, je to obojí stejně rychlé, url neobsahuje nadbytečné informace, není potřeba v routeru používat FILTER_IN/OUT.

Složitější je, když se titulky článků editují – je potřeba zajistit, aby se staré url přesměrovalo http 301 na nové, tedy uchovat v databázi starý slug s odkazem na nový, tento problém ale existuje i když je url ve tvaru /article/123-nejaky-titulek-clanku.

Titulek článku se může editovat vícekrát a je potřeba uchovat všechny původní verze url, které přesměrovávají na poslední verzi.

Nemyslím, že použití id záznamu v url něco ulehčí, nebo zrychlí routování.

Marek Znojil
Člen | 76
+
+3
-

m.brecher napsal(a):

Já to řeším tak, že pro články používám url ve tvaru /article/nejaky-titulek-clanku

slug /nejaky-titulek-clanku se odvodí z title článku (Nette\Utils\Strings::webalize()) .

Pole slug v tabulce article má nastaven unikátní klíč. Před uložením slugu nového/editovaného článku kontroluji zda již existuje a pokud ano, tak přidám číslo verze /article/nejaky-titulek-clanku-verze-1 a opakuji test zda url existuje, až se najde volné.

Článek se nevyhledá podle id, ale podle slug, je to obojí stejně rychlé, url neobsahuje nadbytečné informace, není potřeba v routeru používat FILTER_IN/OUT.

Složitější je, když se titulky článků editují – je potřeba zajistit, aby se staré url přesměrovalo http 301 na nové, tedy uchovat v databázi starý slug s odkazem na nový, tento problém ale existuje i když je url ve tvaru /article/123-nejaky-titulek-clanku.

Titulek článku se může editovat vícekrát a je potřeba uchovat všechny původní verze url, které přesměrovávají na poslední verzi.

Nemyslím, že použití id záznamu v url něco ulehčí, nebo zrychlí routování.

Právě že s použitím id záznamu v url je toto intuitivnější a nemusíš řešit různé verze slugu či dokonce uchovávat jejich historii. Ve FILTER_OUT jednoduše najdeš shodu dle id a do parametrů poté dosadíš onen slug, pokud je rozdílný či dokonce chybí, provede se přesměrování na aktuální verzi.

Jak vzpomenul @PavelKravčík, tuto logiku aplikuje i Nette fórum.

m.brecher
Generous Backer | 735
+
-5
-

@MarekZnojil

Přesměrování jakéhokoliv slugu na aktuální verzi je antipattern, protože neexistující slug by měl vyhodit 404, aby bylo možno chyby testovat a odstraňovat. Chybu je potřeba přiznat, nikoliv zametat pod stůl přesměrováním. Proto sice je možné použít v url id i slug dohromady, ale jak jsem psal, stejně je potřeba uchovat historii slugů, aby se dalo rozlišit zda nenalezené url je 301 nebo 404. Víceméně všechny veřejné cms-ka by měly mít cool url a kód který řeší pěkné url + správné rozlišení 301 nebo 404 si vývojář napíše a odladí jednou a pak už to pouze do dalších cms implementuje.

Pavel Kravčík
Člen | 1180
+
+3
-

@m.brecher: Určitě to není antipattern. ;) Počet různých use-case se blíží nekonečnu. Tvůj způsob bude třeba hrozně user-unfriendly u projektu, kde nezáleží na SEO a často se mění název a nemá smysl držet historii slugů (nestojí to za to programovat).

m.brecher
Generous Backer | 735
+
0
-

Pavel Kravčík napsal(a):

@m.brecher: Určitě to není antipattern. ;) Počet různých use-case se blíží nekonečnu. Tvůj způsob bude třeba hrozně user-unfriendly u projektu, kde nezáleží na SEO a často se mění název a nemá smysl držet historii slugů (nestojí to za to programovat).

Ale jo, každý pattern má někdy výjimky, kdy se vyplatí ho nedodržet – u nenáročných webů se asi nevyplatí s tím rozlišováním 301/404 ztrácet čas.

David Kudera
Člen | 455
+
0
-

m.brecher napsal(a):

Pavel Kravčík napsal(a):

@m.brecher: Určitě to není antipattern. ;) Počet různých use-case se blíží nekonečnu. Tvůj způsob bude třeba hrozně user-unfriendly u projektu, kde nezáleží na SEO a často se mění název a nemá smysl držet historii slugů (nestojí to za to programovat).

Ale jo, každý pattern má někdy výjimky, kdy se vyplatí ho nedodržet – u nenáročných webů se asi nevyplatí s tím rozlišováním 301/404 ztrácet čas.

Pár náhodných článků:

Během těch 3 minut, co jsem tomu teď věnoval, jsem narazil na 2 weby, které vracely 404 (bbc, theguardian). Ti ale mají jiný formát URL, takže to jinak ani nejde.

Tím ale neříkám, že by se měli věci dělat vždy podle toho, jak to mají ostatní. Spíš jen to, že řešit nějak moc 404 je tady možná zbytečné.

Editoval David Kudera (27. 8. 2022 18:33)

Pavel Kravčík
Člen | 1180
+
+2
-

@DavidKudera: Ano pokud mají vyřešenou kanonizaci, tak nemá cenu uživatele šikanovat a nabídnout mu obsah. Poskytovat obsah je vlastně smysl celého webu většinou.

Marek Znojil
Člen | 76
+
+3
-

@mbrecher
Tebe ale ve výsledku bude zajímat co nabídne vyhledávač uživateli. Jedná se o kanonizaci, kterou framework v tomto případě řeší za tebe a díky tomu vyhledávač nezaindexuje všechny varianty, ale pouze tu jednu správnou (Přesměrování 301.), která se případně zobrazí ve vyhledávání.

A na tom, že někdo ručně zkusí změnit url a přesměruješ ho na ten správný tvar není nic špatného. Tebe přeci zajímá co a jak to vyhledávač zaindexuje, zkrátka se postarat o to, aby nedostal různé url s duplicitním obsahem.

Editoval Marek Znojil (27. 8. 2022 20:08)

Bulldog
Člen | 110
+
0
-

froggy napsal(a):

Doplním, že u varianty 2 a 3 je potřeba vyřešit správný redirect po přejmenování slugu pro případ, že se slug bere vždy z aktuálního názvu článku – 301 moved permanently. U varianty 2 je to jednodušší, u varianty 3 je třeba uchovávat tabulku se všemi přejmenovanými slugy.

Ještě doplním, že u varianty 3 kromě tabulky se všemi přejmenovanými slugy je nutné ošetřit, aby nový článek neměl stejný slug jako jiný článek v minulosti (u variant 1 a 2 to zajišťuje ID) V případě totiž, že by vyšel článek ‚aaa‘ pak se přejmenoval na ‚bbb‘ v tabulce by tedy bylo něco jako
[
‚aaa‘ ⇒ ‚bbb‘,
]
prohlížeče by si jej zaregistrovaly správně s 301 redirectem a poté by vznikl opět článek ‚aaa‘, tak by nastala nekonzistence jak v kódu (najednou by neměl přesměrovávat) tak u prohlížečů ty by pod tou URL měly redirect ale nově by vracela 200OK.
Aby se tomu zabránilo, tak nový slug musí být unikátní jak v rámci aktuálních tak všech dříve použitých slugů.
To je mimo jiné důvod, proč radši než 3 se používá 2 u ostatních větších webů, jak psal tazatel. Méně práce se slugy.

@mbrecher

mbrecher napsal(a):

Přesměrování jakéhokoliv slugu na aktuální verzi je antipattern, protože neexistující slug by měl vyhodit 404, aby bylo možno chyby testovat a odstraňovat.

Nic jako neexistující slug neexistuje.
Pravda, pokud jsi to myslel tak, že slug odkazující na neexistující stránku by měl vyhodit 404, tak to máš zcela pravdu.
Jediný rozdíl tu je ten, že celá diskuse nemluví o neexistujících stránkách, které mají vyhazovat 404, ale o existujících stránkách, které se pouze přesunuly pod jiný slug, tedy 301. Pokud by jsi to tak neudělal a dal radši 404, tak se pak stane, že vyhledávač při indexaci narazí na novou stránku, zaindexuje ji a zjistí, že má stejný obsah jako jiná stránka, kterou zaregistroval dříve. To bude mít za následek to, že si vyhledávač bude myslet, že se snažíš vytvořit duplicitní stránku za účelem lepšího vyhledávání a rozmělní ti PageRank, takže tvoje stránka nebude tak důvěryhodná jako předtím a ještě se bude hůře hledat ve vyhledávačích.

Další důvod je například, že pokud je ke článkům vložena možnost komentářů přes facebook, nebo twitter, či jinou fičuru, tak ony mají tyto komentáře a hodnocení navázaná na určitou URL, tedy na ten tvůj pak už neexistující slug. Takže pokud budeš mít článek, který má 1000 liků a 20k komentů a změníš slug tak v případě, že starý bude 404, tak jsou komentáře zničeny, případně se připíší novému článku, který tento ‚neexistující‘ slug jak píšeš dostane. Pokud ale přesměruješ s 301, tak se všechny komenty i liky převedou na nový článek. Toto imho platí i pro reakci na froggyho

m.brecher
Generous Backer | 735
+
+1
-

@Bulldog

neexistující slug? Měl jsem na mysli takový slug, který nikdy na webu použit nebyl, ale přijde na něj http request – překlepem v prohlížeči, nebo v odkazu na jiném webu. Ano, pro realizaci nejhezčího url ve tvaru /clanky/slug je nutná doživotní archivace slugů. Plus implementovat např. tento algoritmus:

  • všechny v historii webu použité slugy je potřeba do konce života webu archivovat:
    • state = moved + odkazem na nový slug
    • nebo state = deleted
  • při vytváření nového slugu se nesmí použít starý, který se nepoužívá, ale je v archivu

    při duplicitě např. přidat nějaký rozlišující index

    nový slug označit state = active

a pak už je to jednoduché, článek hledáme ne podle id, ale podle slugu:

  • slug nalezen, stat = active ⇒ 200
  • slug nalezen, stat = moved ⇒ 301
  • slug nalezen, stat = deleted ⇒ 404
  • slug nenalezen ⇒ 404

Alternativně lze mazané články rovnou smazat a archivovat se nemusí, fungovat to bude stejně. Zrovna tak lze při mazání přesměrovaného článku mazat i existující staré verze, které na něj přesměrovávají.

V praxi se po publikaci nedělá moc často, že by se měnil titulek článku, nebo dokonce se měnil mnohokrát za sebou. Pak nevadí smazané/přesunuté články kterých bude pár kusů jednoduše ponechat v tabulce article, unikátní index nad sloupcem slug zajistí unicitu, není potřeba vytvářet samostatnou tabulku pro archivované slugy. Je to deset/dvacet řádků kódu a jeden pattern je splněn na 100%. A url vypadá jak má.

Důležité je, že volba id x slug nijak nevyžaduje použití komplikovaných filtrů FILTER_IN/OUT v Routeru jak původně uvažoval @koren . A to je téma diskuze. Mít Router jednoduchý a zvolit si verzi /clanky/id-slug nebo /clanky/slug.

mystik
Člen | 289
+
+1
-

Tak reseni s tim ze se preklepy a podobne chyby redirectuji na spravnou URL doporucuje i Google. 404 znamena ze obsah nelze nalezt. Pokud ho lze nalezt (uzivatel zadal starou url nebo se preklepl) je zcela ok redirectnout na spravne misto.

Vyjadreni Google treba viz https://developers.google.com/…to-soft-404s

Should I 301-redirect misspelled 404s to the correct URL?

Redirecting/301-ing 404s is a good idea when it's helpful to users (that is, not confusing like soft 404s).

m.brecher
Generous Backer | 735
+
0
-

@mystik díky za zajímavou informaci, korigovat případné překlepy v url a přesměrovat na správné je zajímavá myšlenka. V přesměrování všech neexistujících slugů na existující není z hlediska SEO asi žádný problém. Google nemá rád falešné 404, kdy se na všech neexistujících url místo korektní 404 zobrazí 200 s informací, že stránka již neexistuje. Toto doporučují někteří seo experti, protože si myslí, že se tím podaří zachytit traffic a page rank ze starých zpětných odkazů, které odkazují na již neexistující stránky. Je ale potom obtížnější hledat reálné chyby v odkazech přímo na webu.

mystik
Člen | 289
+
+4
-

@m.brecher Ano vracet 200 i v pripade ze nevim kam uzivatel chtel je prave to „soft 404“ o kterem tam mluvi a to je chyba. Podobne vsechny neexistujici stranky presmerovavat na homepage nebo mapu stranek. V pripade ze ale z URL vim jiste nebo temer jiste kam uzivatel chtel je z pohkedu uzivatele i SEO lepsi ho tam presmerovat nez mu hodit 404 a nechat ho tapat.

koren
Člen | 59
+
0
-

Díky všem za plodnou diskuzi! Nejvíc zaujal návrh od @Michalek , protože to zní nejpoužitelněji a zároveň se nemusí nějak extra řešit ten slug (nicméně i tak tam je, což je fajn pro SEO)…

Nebyl bys tedy prosím tý dobroty a nerozepsal v krátkosti, jak přesně to máš udělaný? Tj. především jak vypadá:

  1. action / render metoda
  2. routovací funkce
  3. generování odkazu v šabloně

Já nějaký nápady v hlavě mám, ale vypadá to, že už to máš promyšlený, tak budu rád za nasměrování ;) Samozřejmě čím víc info, tím líp, ale z věcí výše už to pak snad dál domyslím.

U toho generování odkazu v šabloně mě např. zajímá, jestli to umí generovat i odkaz přímo se slugem nebo ne (to si totiž dokážu představit jen pokud by router využíval FILTER)… Leda to tam pak šoupnout trochu punkově jako např. <a href="{link article, $article->id}-{$article->slug}">, ale to se mi zdá takový nepěkný :)

(btw. nápad se zkracovačem taky super!)

Editoval koren (1. 9. 2022 18:12)

Michalek
Člen | 210
+
+1
-

Uf, je to pekelně dlouho, co jsem to vymýšlel a vymakaný to není (zdejší experti by to v roce 2022 řešili určitě jinak), ale kouknu do kódu a nastíním… A koukám, že musím opravit nepoužívání filtru. Už jsem zapomněl že ho používám, nevěděl jsem, že to umím :D

Odkaz v šabloně (předávám celý načtený $node z databáze [id, slug, datum, obsah…], protože ho stejně mám načtený a používám pro zobrazování obsahu):

<a n:href=":Article:Frontend:, node => $node">

s čímž souvisí router, který si z pole přes filtr vytáhne jen id a slug

$router[] = new Route('//www.%domain%/clanek/[<id [0-9]+>[-[<seo>]/]]', array(
 NULL => array(
  Route::FILTER_OUT => array($this, 'filterOutNode'),
 ),
 'module' => 'Article',
   'presenter' => 'Frontend',
   'action' => 'default',
 ));

function filterOutNode($params) {
  if (!empty($params['node'])) {
      $params['id'] = $params['node']->node_id;
      $params['seo'] = $params['node']->seo_cs;
      unset($params['node']);
  }
  return $params;
}

a ta kontrolu aktuálnosti slugu už je pak „jednoduchá“, prostě se zkontroluje slug předaný do action se slugem z databáze a když neodpovídá, redirectne se to na správnou verzi

public function actionDefault($id, $seo) {
 ...
 $node = $db->loadById($id); // example
 ($node->seo_cs == $seo) ?: $this->redirect('this', ['node' => $node]);
 ...
}

Je to ořezaný na základ, kolem mám samozřejmě ještě cache výstupů a tak, abych měl nulový počet dotazů do DB při opakovaném zobrazování, ale to už musí každý sám…

Editoval Michalek (2. 9. 2022 1:05)

koren
Člen | 59
+
0
-

Díky moc @Michalek ! :)

Navedlo mě k to vlastnímu řešení, kde skutečně není potřeba používat FILTER (a přesto se to chová tak, jak jsi popisoval)… Základem všeho zůstává směrování podle ID a slug je tam v podstatě jen taková ozdoba pro SEO… Pokud dojde ke změně slugu v DB, ničemu to nevadí, protože presenter to vždy přesměruje na ten aktuální…

Kdyby s tím tedy někdo taky časem zápasil, přikládám moje lehce osekaný řešení (ve výsledku je to mnohem jednodušší, než jsem čekal):

Router:

$router->withModule('Front')->addRoute('clanek/<id [0-9]+>[-<slug>]', [
	'presenter' => 'Article',
	'action' => 'item',
]);

Presenter:

public function actionItem(int $id = NULL, string $slug = NULL): void
{
	$article = $this->articles->getItem($id);
	if($article->slug != $slug) {
		$this->redirectPermanent('this', array('slug' => $article->slug));
	}
}

Šablona:

<a n:href="Article:item, id: $article->id, slug: $article->slug">

Jediný, nad čím přemýšlím, který to přesměrování při nesedícím slugu použít. S ohledem na SEO mi z toho ale intuitivně nejlíp vychází redirectPermanent, aby to jelo pomocí 301…

Editoval koren (9. 9. 2022 17:46)

mystik
Člen | 289
+
+3
-

Urcite pouzit 301 (redirectPetmanent)

Marek Znojil
Člen | 76
+
+1
-

@koren
Pokud bys nechtěl ‚otrocky‘ všude doplňovat slug při tvorbě url a zároveň ho dát pryč z action metody, můžeš zkusit použít toto:

$router->withModule('Front')->addRoute('clanek/<id>[-[<slug>]]', [
  'presenter' => 'Article',
  'action' => 'item',
  'id' => null,
  null => [
    Route::FILTER_OUT => function (array $params): array{
        $params['slug'] = null;  // výchozí hodnota slugu.

        // Zjištění správného slugu z databáze (keš).

        return $params;
    },
  ],
]);

Díky tomuto při tvorbě url ti stačí už pouze doplnit do parametrů id. Z action metody parametr slug taky zmizí, routa se o přesměrování postará.

koren
Člen | 59
+
0
-

Díky @MarekZnojil – k něčemu takovému mířil ten můj původní dotaz :)

Vlastně ale nevím, jak je to s tím cachováním :) S ohledem na to mi to moje řešení přijde přehlednější a míň tricky… Navíc tu část UI s odkazem na článek includuju do šablon napříč webem jako takovou malou komponentu, takže to vkládání slugu do šablony se děje jen na několika málo místech a žádná velká otročina se nekoná :))

I tak mi to ale nedá, abych se trochu nedovzdělal a přemýšlím, jak by se řešilo to cachování ve tvém příkladu… Osobně cache v Nette používám tak jak je, tj. nechávám jí běžet zcela automaticky a maximálně jí při nějakém updatu smažu… Kdyby se ale řešilo to cachování slugů z DB (tak jak naznačuješ), muselo by se řešit ještě něco nad rámec té automaticky generované cache?

Editoval koren (9. 9. 2022 19:14)

m.brecher
Generous Backer | 735
+
+1
-

@koren a @mystik V trvalém provozu použít pro přesměrování 301, ale při testování a ladění krátkodobě používat 302, protože prohlížeče 301 cachují, zatímco 302 ne.

Marek Znojil
Člen | 76
+
+1
-

@koren
V tomto konkrétním případě bych to řešil nějak takto:

// $cache = ..; https://doc.nette.org/cs/caching#toc-zakladni-pouziti

$slugs = $cache->load('article', function (&$dependencies): array{
    $dependencies[\Nette\Caching\Cache::EXPIRE] = '10 minutes';  // Dle potřeby.

    // return ...; data z databáze jako asociativní pole id => slug.
});

A konečné použití ve filtru:

Route::FILTER_OUT => function (array $params) use ($slugs): array{
    $params['slug'] = array_key_exists($params['id'], $slugs) === true ? $slugs[$params['id']] : null;

    return $params;
}

Editoval Marek Znojil (10. 9. 2022 8:59)

Bulldog
Člen | 110
+
+1
-

koren napsal(a):

Jediný, nad čím přemýšlím, který to přesměrování při nesedícím slugu použít. S ohledem na SEO mi z toho ale intuitivně nejlíp vychází redirectPermanent, aby to jelo pomocí 301…

@koren
Tvoje řešení je super, ale pořád trošku složité. Presentery přesně pro tento případ mají metodu canonicalize takže tvůj kód by měl správně být takto:

public function actionItem(int $id = NULL, string $slug = NULL): void
{
	$article = $this->articles->getItem($id);
	$this->canonicalize('this', array('id' => $id, 'slug' => $article->slug));
}

Zbytek kódu stejný jak jsi popsal a ten if a redirect dělá metoda canonicalize interně

koren
Člen | 59
+
0
-

@Bulldog
Ha, super! Tohle jsem neznal!

Jsem si právě říkal, jestli není žádoucí dávat s ohledem na SEO do hlavičky i nějaké meta typu níže, ale při použití canonicalize to asi ani nebude třeba… Nebo má smysl to tam mít i tak?

<link rel="canonical" href="https://domena.cz/clanek/{$article->id}-{$article->slug}" />
Bulldog
Člen | 110
+
+2
-

@koren
Ani s tvým řešením redirectPermanent by jsi tento link tag nemusel použít. Ten se používá v případě, že obě URL adresy jsou platné. Například, pokud máš stránky jak s www. na začátku, tak bez něj a na adrese https://domena.cz máš stejný obsah jako na adrese https://www.domena.cz pak se do hlavičky dá tento link tag, který vyhledávačům řekne, že o duplicitě obsahu víš a že chceš, aby braly v potaz jen jednu z těch URL a tedy aby nerozmělnily PageRank.

Někdo to používá třeba i pokud má vícejazyčný web, tak je tam vlastně totožný obsah jen v jiném jazyce, tak si pomocí link rel="canonical" zvolí jeden jazyk jako výchozí. Toto použití je sice sporné, ale proti gustu. :)

Tedy ne, pokud používáš redirectPermanent, canonicalize, nebo v routeru třeba $router::ONE_WAY, případně jiný 301 redirect, tak link rel="canonical" není potřeba. :)

Editoval Bulldog (10. 9. 2022 16:48)

Skřetík
Člen | 11
+
+1
-

Sice oživuju mrtvolu, ale představte si situaci s „novým občanským zákoníkem“. Napíšete článek

  • v květnu 2022, že je více komárů /clanek/letos-je-vice-komaru/
  • v lednu 2023 ten článek svědomitě upravíte, protože už to není letos ale loni, takže budete mít /clanek/2022-bylo-vice-komaru/, a z /clanek/letos-je-vice-komaru/ povede redirekt 301
  • a v květnu 2023 napíšete „nový“ článek /clanek/letos-je-vice-komaru/

Problém je v tom, že 301 je permanent redirect, a prohlížeč si tohle ukládá do cache, takže pokud jste mezi lednem a květnem 2023 navštívili stránku /clanek/letos-je-vice-komaru/, prohlížeč si pamatuje, že toto má být „navždy“ přesměrované na /clanek/2022-bylo-vice-komaru/. Takže vy sice v googlu najdete článek o tom, že letos je více komárů (protože google má zaindexovaný článek z roku 2023), ale když na to kliknete, váš prohlížeč ví, že tohle má „navždy“ přesměrovávat. A past je v tom, že tohle už ze serveru afaik neovlivníte, uživatel si buď musí vymazat mezipaměť prohlížeče, nebo zapnout developer tools které mezipaměť defaultně vypínají.

Takže s tou 301 bych byl opatrný, už jsem na to parkrát narazil když klient chtěl přesměrovat jednu doménu na druhou, a pak se jednoho dne rozhodl, že na tu doménu přece jen udělá separátní obsah.

Editoval Skřetík (10. 10. 2023 15:24)