Een merkfilter hoort de catalogus in te perken tot Nike, Adidas of het huismerk, zonder bij elke klik de term-tabellen opnieuw te joinen. In WooCommerce is een merk een taxonomie, net als een categorie, alleen plat: een product hoort bij Nike, niet bij een kind van Nike. InstantFilter behandelt dat merkenarchief als context. De eerste HTML is server-side. Elke volgende klik op merk, maat of kleur gebeurt in de browser, in 1,5ms tot 5ms, met 0 SQL.
Wat is filteren op merk in WooCommerce?
Er zijn twee plekken waar een bezoeker “merk” verwacht, en die moet je niet door elkaar halen.
- Het merkenarchief. De URL is het merk zelf, bijvoorbeeld
/merk/nike/. De verzameling is alles van dat merk. Daarbinnen filtert de bezoeker nog op categorie, maat, kleur en prijs. - Het merkfacet op een categorie. De URL is “Heren > Sneakers”. Merk is één van de filters naast maat en kleur. De bezoeker perkt die categorie in tot één of meer merken.
Beide zijn archieffilters, geen sitewide zoekopdracht. Wie “Nike” in de header typt, start een zoekmachine. Wie op het merkenarchief of het merkfacet klikt, maakt een doorsnede. Dat onderscheid staat in de gids zoeken vs filteren op categorie-archieven. Een merknaam in de zoekbalk en een merkterm in de filter zijn niet dezelfde index.
WooCommerce heeft een eigen merktaxonomie: product_brand. Plugins die merken eerder hebben geïntroduceerd, registreren vaak een eigen taxonomie. Perfect Brands for WooCommerce gebruikt pwb-brand. InstantFilter ziet beide, plus elke andere publieke product-taxonomie, als archiefcontext. Attributen die met pa_ beginnen — een globaal attribuut “Merk” — zijn geen taxonomie-archief in die picker. Die lopen als facet-eigenschap op het product, niet als /merk/nike/, tenzij je ze zelf als taxonomie-archief hebt ingericht.
Waarom worden merkfilters traag door term-joins?
Een product koppelt aan een merk via wp_term_relationships. De term zelf staat in wp_terms, de taxonomie-koppeling in wp_term_taxonomy. Een AJAX-filter dat “alleen Nike, maat 42, blauw” wil, bouwt een tax_query met meerdere van die joins, en vaak nog een meta_query op wp_postmeta voor prijs of voorraad. Elke extra facet is een extra join. Elke bezoeker die klikt, start WordPress via admin-ajax.php en bezet een PHP-worker.
Op een shop met een handvol merken valt dat nog mee. Op een multi-brand catalogus met duizenden SKU’s en tientallen merken groeit wp_term_relationships mee met elk product. De query moet dan niet alleen “dit merk” vinden, maar ook de doorsnede met categorie, maat en kleur, plus een COUNT per resterende optie. Dat is dezelfde klasse probleem als trage attribuutfilters: de database doet per klik werk dat de browser kan doen zodra de set bekend is. Bij tienduizenden producten wordt die klik de spinner van 800ms tot 2,5 seconden.
Een page-cache helpt de merk-URL bij de eerste load, en faalt zodra de filter een unieke AJAX-combinatie wordt. Elke merk-plus-maat-combinatie is een cache-miss. De archiefpagina blijft alleen cachebaar als de klik de server niet meer nodig heeft.
Hoe begrenst InstantFilter een merkenarchief?
Staat de bezoeker op het archief van één merk, dan is dat merk de context, net als een categorie. FilterContext neemt elke product-taxonomie mee die aan product hangt, inclusief product_brand. WooCommerce-categorie en -tag zitten daar altijd bij, ook als een request de taxonomie nog niet geregistreerd heeft. Via de filter if_context_taxonomies kun je een taxonomie toevoegen of uitsluiten.
De serverquery voor die eerste render zoekt niet in wp_term_relationships per facetklik. Hij begrenst de listing via if_terms_map: welke geïndexeerde items aan deze term hangen. Een hiërarchische taxonomie, zoals een categorie, neemt child-terms mee. Een merk is plat. get_term_children() geeft dan niets terug, en dat is het gewenste gedrag: alleen Nike, niet een denkbeeldig kind van Nike.
Die begrenzing gebeurt bij het opbouwen van de set, niet bij elke klik daarna. Na hydratatie staan die items in de browser als contextItems. Maat, kleur en prijs filteren daarop, met 0 SQL. De tellers werken zoals in de gids over dynamische facet-tellingen: een optie op 0 verdwijnt, de gekozen optie blijft staan, en de telling van een facet negeert de eigen keuze van dat facet.
Hoe werkt een merkfacet binnen een categorie?
Op “Sneakers” is merk geen archief, maar een facet. Het product draagt het merk als term of als eigenschap in het codebook. Een klik op Nike filtert contextItems van die categorie tot de items met die merkcode. De inverted index koppelt de optie aan item-id’s als het codebook die index voor de eigenschap heeft. Anders vergelijkt de engine de code op item.props.
De teller “Nike (24)” is het aantal producten in de huidige doorsnede, niet een vooraf vastgetimmerd getal uit de term-count van WordPress. Die term-count is het aantal producten met dat merk in de hele shop, of in het gunstigste geval in de categorie bij het laden. Zodra de bezoeker maat 42 kiest, hoort Nike alleen nog de sneakers te tellen die én Nike én maat 42 zijn. Een merk dat dan op 0 valt, verdwijnt uit de lijst. Het merk dat aanstaat, blijft zichtbaar, zodat de bezoeker het uit kan zetten.
Meerdere merken aanvinken volgt dezelfde self-exclude regel. De grid houdt Nike én Adidas aan. De tellers binnen het merkblok negeren de merkkeuze en houden maat en kleur vast. Zo zie je of Puma nog iets toevoegt vóórdat je het aanvinkt. Dat getal kan hoger zijn dan het aantal kaarten, omdat de kaarten de merkkeuze wél volgen en de teller in het merkblok hem negeert.
Waarom is een merk geen categorie met kinderen?
Categorieën zijn hiërarchisch. “Schoenen” telt “Sneakers” mee, omdat get_term_children() die termen teruggeeft en if_terms_map ze in de set stopt. Merken zijn dat niet. Een archief Nike mag geen producten van een ander merk tonen omdat iemand ergens een child-term heeft aangemaakt die de plugin niet verwacht. InstantFilter volgt de taxonomie: plat blijft plat.
Dat scheelt ook een klasse fouten in de teller. Een parent-categorie die children meetelt, hoort een hoger getal te tonen dan de optelling van alleen de parent-term. Een merk dat stiekem children meetelt, toont producten die de URL niet belooft. De code doet het omgekeerde: geen children, dus de URL en de set blijven hetzelfde merk.
Gebruik je een merk tóch als hiërarchie — een huismerk met sublijnen — dan gedraagt die taxonomie zich als een categorie, inclusief children, zodra WordPress haar als hiërarchisch registreert. De engine kijkt niet naar de naam “brand”. Hij kijkt of get_term_children() iets teruggeeft.
Hoe verhouden een term-join, een attribuutfilter en InstantFilter zich?
| AJAX op wp_term_relationships | Merk als pa_-attribuut | InstantFilter op de merktaxonomie | |
|---|---|---|---|
| Waar het merk leeft | Term-tabellen, elke klik opnieuw | Globaal attribuut, vaak ook in postmeta | Taxonomie, geïndexeerd in if_terms_map |
| Archief-URL | Alleen als de taxonomie een archief heeft | Meestal geen eigen archief | Ja, als de taxonomie een archief is |
| Child-merken | Afhankelijk van de query | Niet van toepassing | Nee, tenzij de taxonomie hiërarchisch is |
| SQL per klik | Joins plus COUNT per optie | meta_query of tax_query | 0 na hydratatie |
| Teller na maat 42 | Nieuwe query | Nieuwe query | In het geheugen |
| Latency | 800ms – 2.500ms | Zelfde klasse als AJAX | 1,5ms – 5ms |
De architectuur achter die laatste kolom is dezelfde als in AJAX vs frontend-first filtering. Het merk voegt geen tweede engine toe. Het is een term in de set die de browser al heeft.
Hoe zet je een merkenarchief en een merkfacet neer?
Je vervangt de merktaxonomie niet. Je stopt het archief en het facet in dezelfde engine. De opmaak van het raster, de kaart en het merkfilter staat in de Layout Builder, niet in een loop van de page builder.
- Kies de taxonomie die de shop al gebruikt. Dat is
product_brandals WooCommerce de merken beheert, ofpwb-brandals Perfect Brands ze beheert. Zet niet een tweede merkenlijst ernaast als attribuut, tenzij je bewust twee systemen wilt onderhouden. - Archief laten respecteren. Op het merk-template moet de listing de WordPress-context volgen. Anders toont
/merk/nike/de hele catalogus en is het merk alleen een los facet. - Plaatsen. In Bricks een van de vier native elementen in het taxonomy-template. In Elementor het archief-shortcode. Op een categorie plaats je het merk als facet in dezelfde listing.
- Merk op de kaart. De kaart kan
taxonomy.product_brandtonen. Die bron zit in de card-presets. Het label op de kaart is weergave. De filter leest de geïndexeerde term, niet de tekst op de kaart. - Controleren. Open een merkarchief. De eerste HTML bevat alleen producten van dat merk. Klik een maat. De andere maten die dat merk niet in die maat heeft, verdwijnen, zonder verzoek naar
admin-ajax.php. Open daarna een categorie en klik één merk. De teller van de andere merken verandert mee. Een merk op 0 verdwijnt. Het merk dat aanstaat, blijft staan.
Wat schrijft de index weg voor een merk?
Bij het indexeren koppelt InstantFilter elk item aan zijn termen in if_terms_map. Die tabel is het spoor dat een filterklik anders in wp_term_relationships zou zoeken. De SSR-query van een merkenarchief joint daar één keer op: item_id plus de taxonomie, beperkt tot het term-id van dat merk. Staat de listing op een eigen query in plaats van op het archief, dan is het dezelfde tabel, maar als EXISTS, en alleen als de listing de optie respect_context aan heeft staan.
Zonder die optie negeert een listing het merk in de URL. De pagina heet Nike en het raster is de hele catalogus. Het merk is dan hooguit een vinkje dat de bezoeker zelf nog moet zetten. Op een echt archief — de query-context is archive, niet een losse listing — geldt die begrenzing altijd. FilterContext zet de query-var van de taxonomie op de slug van de huidige term, of dat nu product_brand is of een eigen merktaxonomie.
De kaartpresets kunnen taxonomy.product_brand als bron gebruiken. Dat is de tekst op de kaart. Het filter leest dezelfde term via de index, niet via die zichtbare tekst. Een ander label op de kaart verandert de facet niet. In de taxonomie-picker van de kaarten staat product_brand bovenaan, daarna pwb-brand, dan categorie en tag. Attributen met pa_ ontbreken daar: die zijn Woo-attributen en horen bij de eigenschappen, niet bij het blok dat een archief-URL kan volgen.
Welke fouten maken multi-brand shops?
Drie fouten zie je terug, en geen van drie is een hostingprobleem.
- Het merk bestaat twee keer. De shop heeft
product_brandén een globaal attribuutpa_merk, gevuld door een import die ze niet gelijk houdt. De archief-URL volgt de taxonomie. Een facet op het attribuut volgt de andere lijst. De bezoeker ziet Nike op de kaart en een andere Nike in de filter. Kies één bron. - De listing op het merk-template volgt de URL niet.
respect_contextstaat uit. De eerste HTML bevat dan al de verkeerde set, en de browser kan die fout niet herstellen: hij filtert alleen wat de server heeft meegegeven. - De teller is de term-count van WordPress. Die count is het aantal producten met die term, bijgewerkt als iemand een product opslaat. Hij weet niets van de maat die de bezoeker net koos. Stel dat de categorie 240 Nike-sneakers heeft. Na maat 42 en blauw kunnen dat er 9 zijn. Die 240 en 9 zijn een rekenvoorbeeld, geen meting. De engine telt de set die over is, niet het getal dat WordPress bij de term bewaart.
Blijft het merkenarchief cachebaar?
De eerste HTML van het merkenarchief is een gewone archiefpagina. Een page-cache kan die URL serveren. De klik op maat of kleur daarna raakt admin-ajax.php niet, dus die combinatie hoeft geen eigen cache-entry te worden. De URL van het merk is cachebaar. De doorsnede leeft in de browser.
Een filter dat bij elke merkklik een unieke AJAX-respons opbouwt, maakt van elke combinatie een cache-miss. Tien merken, acht maten en twaalf kleuren zijn honderden combinaties die de cache nooit warm houdt. De klik in de browser laat de merk-URL één keer cachen. Dat is hetzelfde patroon als bij categorie-archieven, niet een aparte cache-laag voor merken.
Wat zie je als je het test?
Open het merkenarchief met de netwerk-tab. Het document bevat de producten van dat merk in de HTML. Klik daarna een maat. Er hoort geen nieuw document en geen verzoek naar admin-ajax.php bij te komen. De teller van andere facetten mag veranderen, want die maat heeft de set kleiner gemaakt. Het merk zelf blijft de context van de URL. Die context haal je niet weg door een facet uit te zetten.
Op een categorie werkt het omgekeerd. De URL is de categorie. Merk is een vinkje. Nike uitzetten brengt de andere merken terug, binnen die categorie. Heeft het codebook een inverted index voor die eigenschap, dan koppelt de optie aan item-id’s. Zonder die index vergelijkt de engine de code op item.props. Beide paden zijn geheugen, geen SQL. De grid past zich op dat taxonomie-archief aan, ook als het geen categorie of tag is: product_brand en een eigen merktaxonomie horen bij dezelfde check.
Filter op de merktaxonomie als
- Elk product één merk heeft: de URL van het merk en de set moeten hetzelfde zijn.
- Bezoekers binnen een categorie op merk inperken: de teller hoort de doorsnede te zijn, niet de term-count van de hele shop.
- De catalogus groot is: een join op wp_term_relationships per klik schaalt niet; de set in de browser wel, tot het codebook te groot wordt.
Niet deze route verwachten als
- Merk alleen een vrij tekstveld is: zonder taxonomie of attribuut is er geen facet om te tellen.
- Je ook pagina’s op merk wilt vinden: dit is een productarchief, geen header-zoekbalk.
- De catalogus ver boven 100.000 SKU’s zit: dan wordt het codebook de grens, niet de merkterm zelf.
Veelgestelde vragen over WooCommerce filteren op merk
Verdiep je verder in merken en archiefarchitectuur
Een merk is een term in het archief. Deze gidsen gaan door op die architectuur:
- Dynamische facet-tellingen zonder SQL
- Zoeken vs filteren op categorie-archieven
- AJAX vs frontend-first filtering
- Trage queries en wp_postmeta
- 50.000 producten filteren zonder serverload
- Bekijk InstantFilter licenties en prijzen
Filter op merk zonder bij elke klik de term-tabellen te joinen
Test InstantFilter 14 dagen. Open een merkenarchief en een categorie, en kijk of de merkklik in milliseconden landt.