WP_Query er klassen WordPress bruker til å hente innlegg fra databasen. Hver arkivside, søkeresultat og enkeltside starter med én hovedspørring. Sekundære loops er ekstra WP_Query-instanser du legger til i maler, widgets eller shortcodes. Feilen er nesten alltid den samme: for mange spørringer, for brede argumenter, eller feil pagineringsvariabel.
På hosting med PHP-FPM merker du dette først som økt pm.max_children-trykk og trege TTFB-tall, ikke som “langsom SQL” i admin. Denne guiden går gjennom hvordan WP_Query bygger en forespørsel, hovedspørring versus sekundær WP_Query, pre_get_posts, ytelsesflagg, meta_query- og tax_query-feller, paginering, wp_reset_postdata(), feilsøking med Query Monitor, objekt-cache, REST API og når get_posts() er nok.
Offisiell referanse: WP_Query, pre_get_posts, kildekoden i class-wp-query.php og ytelsesoptimalisering.
Slik bygger og kjører WP_Query en forespørsel
For å mestre WP_Query må du forstå hva som skjer når du oppretter en instans. WordPress går gjennom disse stegene:
- Tolker argumentene: arrayet ditt gjøres om til et standardisert format.
- Genererer SQL: WordPress bygger en sammensatt
SELECT-setning. - Kjører spørringen: forespørselen sendes til databasen.
- Fyller objektet:
WP_Query-objektet fylles med innleggsobjekter og metadata.
Egenskaper på WP_Query-objektet
Når du kjører $query = new WP_Query( $args ), får du ikke bare et array av innlegg. Du får et stort objekt med noen egenskaper du kommer til å bruke hele tiden:
$query->posts: et array avWP_Post-objekter.$query->post_count: antall innlegg som vises på gjeldende side.$query->found_posts: totalt antall innlegg som matcher kriteriene, uavhengig av paginering.$query->max_num_pages: totalt antall resultatsider.$query->query_vars: argumentene WordPress faktisk brukte da spørringen ble kjørt.
Hovedspørring eller sekundær WP_Query i WordPress
Hovedspørringen ($wp_the_query / den globale $wp_query) bygges før malen lastes. Den bestemmer hvilken mal WordPress velger (is_home(), is_archive(), is_singular() osv.) og hvilke innlegg have_posts() / the_post() viser i standardloopen.
if ( have_posts() ) :
while ( have_posts() ) :
the_post();
get_template_part( 'template-parts/content', get_post_type() );
endwhile;
endif;En sekundær loop er en ny instans. Den endrer ikke hovedspørringen og skal ikke brukes til å “fikse” arkivfiltre.
$related = new WP_Query(
[
'post_type' => 'post',
'posts_per_page' => 4,
'post__not_in' => [ get_the_ID() ],
'ignore_sticky_posts' => true,
'no_found_rows' => true,
]
);
if ( $related->have_posts() ) {
while ( $related->have_posts() ) {
$related->the_post();
the_title( '<h3>', '</h3>' );
}
wp_reset_postdata();
}Bruk aldri query_posts(). Den erstatter den globale $wp_query med en ny spørring, ødelegger is_*()-betingelser lenger ned på siden og kjører en ny hovedspørring oppå den WordPress allerede har kjørt. Funksjonsreferansen sier selv at den ikke er ment for bruk i plugins eller temaer. Hvis du må endre hva arkivet viser, bruk pre_get_posts. Hvis du trenger ekstra innhold ved siden av hovedlisten, bruk new WP_Query().
Slik bruker du pre_get_posts i WordPress
Referansen for pre_get_posts beskriver hooken som at den kjører etter at query-variabelobjektet er opprettet, men før selve spørringen kjøres. Det er hele poenget: du endrer argumentene én gang, og WordPress kjører fortsatt bare én spørring, i stedet for “hent alt, deretter filtrer i PHP” eller “kjør en ny WP_Query oppå”.
add_action(
'pre_get_posts',
static function ( WP_Query $query ): void {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_home() ) {
$query->set( 'posts_per_page', 12 );
$query->set( 'category__not_in', [ 17 ] ); // f.eks. "Nyhetsbrev".
}
if ( $query->is_post_type_archive( 'case' ) ) {
$query->set( 'orderby', 'menu_order title' );
$query->set( 'order', 'ASC' );
}
}
);Fem regler som sparer timer med feilsøking:
- Kall betingelsene som metoder på objektet du får inn (
$query->is_home()), ikke som globale funksjoner. Den globaleis_main_query()leser$wp_query, ikke spørringen som filtreres. I kjernen erWP_Query::is_main_query()én enkelt sammenligning:$wp_the_query === $this. - Guard med
is_admin()og$query->is_main_query(). Uten begge treffer du adminlister, feeds, REST-forespørsler og hver sekundære loop på siden. is_front_page()fungerer ikke inne i hooken, fordi spørringen ikke er satt opp ennå.is_home()fungerer. For en statisk forside må du se på query-variablene i stedet.- Sett argumenter med
$query->set(), ikke ved å erstatte hele objektet. - Ikke kall
query_posts(),get_posts()ellernew WP_Query()inne i callbacken. Det lager nye spørringer mens du prøver å justere den som bygges.
På WooCommerce-arkiv er hovedspørringen produktlisten. Filtrer der, ikke med en ekstra loop i archive-product.php, hvis målet er samme URL og samme paginering.
Feeds (is_feed()) og søk (is_search()) er også hovedspørringer. Hvis du begrenser post_type på forsiden, sjekk at du ikke utilsiktet tømmer RSS eller søkeresultater. Guard med eksplisitte betingelser per kontekst.
Hva the_post() og have_posts() gjør bak loopen
Leser du class-wp-query.php, forstår du flere feil som ser tilfeldige ut i maler.
- Ved første kall til
the_post()fyller kjernen forfatter-cachen for hele resultatsettet og kjører action-hookenloop_start. Hvis spørringen ikke returnerte fulle innleggsobjekter, fyller den også post-, meta- og term-cachen på dette tidspunktet (med mindreupdate_post_meta_cacheellerupdate_post_term_cacheerfalse). - Med
'fields' => 'ids'gjør dagens kjerne likevel hver ID om til et fullt innleggsobjekt medget_post()inne ithe_post(). Loopen fungerer altså, men da koster ID-listen like mye som en full spørring. setup_postdata()setter flere globale variabler enn$post:$id,$authordata,$currentday,$currentmonth,$page,$pages,$multipage,$moreog$numpages, og kjører deretter action-hookenthe_post. En sekundær loop som ikke nullstilles, lar alle disse peke på det siste relaterte innlegget. Derfor går en forfatterboks eller et paginert innlegg (<!--nextpage-->) i stykker etter en blokk med relaterte innlegg.- Når
have_posts()går tom for innlegg, kjører denloop_endog spoler spørringen tilbake. Hvis spørringen ikke traff noe, kjører denloop_no_resultsi stedet.
Raskere WP_Query med no_found_rows og fields ids
Uten no_found_rows, og så lenge spørringen har LIMIT, legger WP_Query til SQL_CALC_FOUND_ROWS i SELECT og kjører deretter SELECT FOUND_ROWS() (via filteret found_posts_query) for å telle totalt antall treff til pagineringen. Trenger du ikke sidetall, skru det av:
$ids = new WP_Query(
[
'post_type' => 'post',
'posts_per_page' => 20,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
]
);Når du bare trenger ID-er (relaterte innlegg, eksklusjonslister, transient-nøkler), er fields => 'ids' det billigste alternativet: mindre data fra databasen, ingen hydrering av WP_Post-objekter, og du kan fortsatt bruke ID-ene i post__in. Les $q->posts direkte og kjør ikke the_post() over listen, ellers henter kjernen fulle objekter likevel.
update_post_meta_cache og update_post_term_cache kan skrus av når loopen aldri kaller get_post_meta() eller taksonomi-tagger. Hvis malen henter featured image, kategorier eller ACF-felt, la dem stå på true (standard). Å skru dem av og deretter hente meta per innlegg gir N+1-spørringer som er verre enn én samlet cache-fylling.
posts_per_page => -1 er nesten alltid feil i produksjon. Sett et tall. Hvis du virkelig trenger alle ID-er (migrering, CLI), gjør det i WP-CLI eller en bakgrunnsjobb, ikke i en HTTP-forespørsel bak PHP-FPM.
Ytelsesfeil i WP_Query: orderby rand, meta_query og tax_query
Tilfeldig rekkefølge med orderby rand
ORDER BY RAND() får MySQL til å gi hver rad som matcher en tilfeldig verdi og sortere hele settet, vanligvis i en midlertidig tabell, før LIMIT brukes. Kjernen nekter også å cache den: query-cachen fra WordPress 6.1 hopper over alle forespørsler der ORDER BY inneholder RAND(.
Løsningen:
- Hent ID-ene til de siste 50 innleggene.
- Velg 5 tilfeldige ID-er i PHP.
- Kjør en ny spørring med
post__in => $random_ids.
meta_query mot en meta_key uten indeks
Dette er den vanligste enkeltårsaken til trege arkivsider på eldre WooCommerce-installasjoner. schema.php i kjernen oppretter wp_postmeta med to indekser på én kolonne hver, post_id og et prefiks av meta_key, og ingen sammensatt indeks. meta_key-indeksen har for lav kardinalitet til å hjelpe når WordPress filtrerer på meta_key = '_stock_status' AND meta_value = 'instock': MySQL må fortsatt skanne hver rad som har den nøkkelen.
Fra praksis: en polsk WooCommerce-butikk med 50 000 produkter. Butikkarkivet brukte pre_get_posts til å legge på en meta_query som filtrerte på _stock_status og et egendefinert _brand-felt. P95 TTFB på butikksiden var 4,1 sekunder. SAVEQUERIES viste at 80 % av tiden gikk med til én enkelt JOIN mot wp_postmeta. Løsningen:
ALTER TABLE wp_postmeta
ADD INDEX idx_meta_key_post_id (meta_key(32), post_id);Etter indeksen falt den samme spørringen til 180 ms. Ingen PHP ble endret. For verdier som inngår i ekte intervallsammenligninger ('compare' => '>', 'compare' => 'BETWEEN'), trenger du også en indeks på meta_value, som regel en prefiksindeks fordi kolonnen er LONGTEXT.
Når verdiene er et fast sett (størrelser, farger, merkenavn), ikke bruk meta_query i det hele tatt. Registrer en egen taksonomi. wp_term_relationships har primærnøkkel på (object_id, term_taxonomy_id) og en egen indeks på term_taxonomy_id, så det samme filteret som tok 1,8 s som meta_query, kjører på 30-60 ms som tax_query.
tax_query-klausuler summerer seg i stor skala
WP_Tax_Query gjør først termene dine om til term_taxonomy_id-verdier. Deretter legger hver IN-klausul til én LEFT JOIN mot wp_term_relationships. AND-klausuler blir en korrelert COUNT-underspørring, NOT IN en underspørring, og bare EXISTS og NOT EXISTS berører wp_term_taxonomy. Hver ekstra taksonomi i samme spørring gir MySQL enda en join eller underspørring å kombinere, så sider med fasettfiltre som kombinerer seks taksonomier (størrelse, farge, merke, sesong, materiale, prisklasse) er der dette gjør vondt først. Enten denormaliserer du til én fasett-taksonomi, bytter til FacetWP eller SearchWP som indekserer utenfor tabellene, eller flytter fasettene til en søkemotor (Elasticsearch via ElasticPress, eller Algolia for kataloger som bare leses).
Vanlige feil med tax_query i WP_Query
tax_query er kraftig og lett å gjøre dyr. Vanlige feil:
OR mellom taksonomier uten å forstå join-kostnaden. Flere relation => 'OR'-klausuler på store taksonomier gir brede joins. Foretrekk én taksonomi med flere termer (operator => 'IN') når det er mulig.
Slug versus term_id. field => 'slug' er lesbart, men term_id er stabilt når slugen endres. I kode som skal leve lenge, lagre term-ID i config eller options.
Tomme arrays. En tom terms-liste kan gi “ingen treff” eller uventet SQL avhengig av operator. Valider input før du bygger spørringen.
meta_query for filtre som burde vært taksonomi. Meta lagres som EAV (wp_postmeta). Indeksene er svakere enn term-relasjonene. Hvis feltet er et filter (bransje, by, produktlinje), registrer en custom taxonomy i stedet for å søke på meta_key. Den guiden går også dypere inn i sammensatte indekser og query-cachen fra WordPress 6.1.
$args = [
'post_type' => 'post',
'posts_per_page' => 10,
'no_found_rows' => true,
'tax_query' => [
'relation' => 'AND',
[
'taxonomy' => 'category',
'field' => 'term_id',
'terms' => [ 4, 9 ],
'operator' => 'IN',
],
[
'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => [ 'wordpress', 'ytelse' ],
'operator' => 'AND',
],
],
];include_children er true som standard for hierarkiske taksonomier. På flate lister der du vil treffe nøyaktig én term, sett include_children => false eksplisitt.
Slik filtrerer du på dato med date_query i WP_Query
De innebygde datospørringene er kraftige og effektive. Bruk dem i stedet for å filtrere med egen SQL.
$args = [
'date_query' => [
[
'after' => 'January 1st, 2025',
'before' => [
'year' => 2026,
'month' => 12,
'day' => 31,
],
'inclusive' => true,
],
],
];Paginering: paged versus page
WordPress bruker to query-variabler:
pagedfor arkiver, bloggindekser og de fleste tilpassede lister (URL-er med/page/2/).pagepå en statisk forside, og for paginering inne i et innlegg eller en side (<!--nextpage-->).
I en sekundær loop på et arkiv:
$paged = max( 1, (int) get_query_var( 'paged' ) );
$list = new WP_Query(
[
'post_type' => 'post',
'posts_per_page' => 10,
'paged' => $paged,
]
);På en side satt som statisk forside (is_front_page() && is_page()) settes page, ikke paged. Mange temaer feiler her: lenkene ser riktige ut, men loop nummer to starter alltid på side 1, eller side 2 gir 404. Les begge når du er usikker:
$paged = (int) get_query_var( 'paged' );
if ( $paged < 1 ) {
$paged = (int) get_query_var( 'page' );
}
$paged = max( 1, $paged );For pagineringslenker i sekundære loops: send total og current til paginate_links() basert på $list->max_num_pages og $paged, ikke den globale $wp_query. Ellers peker “neste side” på hovedarkivets sidetall.
Ikke kombiner offset med paged. Argumentet offset overstyrer sideberegningen, så hver side returnerer det samme utsnittet. Trenger du “ett fremhevet innlegg, deretter en paginert liste”, kjør to spørringer eller regn ut offset per side selv.
Når loopen ikke skal pagineres, sett no_found_rows => true og hopp over paginate_links(). Du sparer tellespørringen helt.
wp_reset_postdata og globale $post
$related->the_post() setter den globale $post og de andre globale variablene setup_postdata() styrer. Etter loopen må du kalle wp_reset_postdata() slik at resten av malen (sidebar, footer, relaterte widgets) fortsatt ser hovedinnlegget.
$q = new WP_Query( $args );
while ( $q->have_posts() ) {
$q->the_post();
// mal
}
wp_reset_postdata();Forskjellen på wp_reset_postdata og wp_reset_query
I wp-includes/query.php er forskjellen to linjer. wp_reset_postdata() kaller $wp_query->reset_postdata(), som gjenoppretter den globale $post og de andre globale variablene fra hovedspørringen. wp_reset_query() setter først $wp_the_query tilbake i $wp_query og kaller deretter wp_reset_postdata(). Du trenger bare wp_reset_query() etter query_posts(), og der er løsningen å fjerne query_posts().
Når du itererer over et array fra get_posts() med foreach, vet ikke template-taggene om innlegget før du kaller setup_postdata( $post ). Nullstill på samme måte etterpå:
global $post;
$items = get_posts( [ 'numberposts' => 3 ] );
foreach ( $items as $post ) {
setup_postdata( $post );
the_title( '<li>', '</li>' );
}
wp_reset_postdata();setup_postdata() setter ikke selv den globale $post. Derfor er løkkevariabelen her den globale $post. Alternativet er å unngå globale template-tagger og lese $post->post_title direkte.
Glemmer du reset, feiler get_the_ID() i footer, schema-blokker og “les også”-moduler som forventer hovedinnlegget.
Finn trege spørringer med Query Monitor, SAVEQUERIES og EXPLAIN
Fire verktøy finner faktisk svaret:
- Query Monitor (John Blackbourn). Installer på staging, aldri i produksjon. Fanen Queries grupperer etter komponent, så du ser med en gang om TTFB-en kom fra temaet ditt, WooCommerce-kjernen eller en tredjeparts plugin som hekter seg på
pre_get_posts. Arbeidsflyt som fungerer:- Åpne siden som føles treg (arkiv, produktliste, forside med tre “featured”-blokker).
- Sorter Queries på tid. Den tregeste spørringen er nesten alltid den som skal fikses.
- Sjekk Duplicate Queries. Den fanger N+1-loops der en template-tag inne i
the_post()spør databasen på nytt for hver rad, og ofte sammetax_queryi header, hero og hovedliste. - Se Hooks: bekreft at
pre_get_posts-callbacken din faktisk kjører på hovedspørringen.
SAVEQUERIES. Settdefine( 'SAVEQUERIES', true );iwp-config.php, bare på staging, fordi hver spørring i forespørselen holdes i minnet. WordPress fyller da$wpdb->queries. Hver oppføring inneholder SQL-en, varigheten, kallstacken, starttidspunktet og valgfrie egne data (wpdb::log_query()i kjernen).error_log( print_r( $wpdb->queries, true ) )påshutdowngir den klassiske oversikten over hva som kjørte og hvor lang tid det tok.$query->requestplussEXPLAIN. Etternew WP_Query()skriverecho $query->request;ut SQL-en WordPress faktisk genererte. Kjør strengen underEXPLAINi TablePlus eller medmysql -e. Se ettertype: ALL(full tabellskanning),Using filesortellerUsing temporary: det er radene som trenger en indeks.- WP-CLI.
wp db query --skip-column-names "EXPLAIN $SQL"kjørerEXPLAINmot den levende databasen uten nettleseren i mellom.wp post list --post_type=product --fields=ID --posts_per_page=10gjenskaper spørringslogikken i et forutsigbart miljø, nyttig når den trege siden ligger bak betalingsmur eller innlogging.
En tommelfingerregel for prioritering: alle spørringer over 50 ms er mistenkelige med varm cache, alt over 200 ms er ødelagt. web.dev setter en god TTFB til 0,8 sekunder eller mindre. Spiser én enkelt WP_Query 250 ms av det, er en stor del av budsjettet brukt før malen i det hele tatt begynner å rendre. TTFB er ikke selv en Core Web Vital, men den er første del av LCP-tiden, så hvert millisekund av den viser seg i LCP.
Ti ekstra WP_Query på forsiden multipliseres under kampanjetrafikk, særlig med lav memory_limit og få PHP-FPM-workere.
Objekt-cache og WP_Query
Siden WordPress 6.1 cacher kjernen ID-listen for gjentatte, identiske spørringer i objekt-cachen (cache_results). Den varmer også post-, meta- og term-cachene for ID-ene den henter. Med Redis eller Memcached som persistent objekt-cache overlever dette mellom forespørsler og gjør gjentatte get_post()- og meta-oppslag billigere. Uten persistent objekt-cache lever cachen bare i én forespørsel, og den samme cache-fyllingen kjøres på nytt ved hver forespørsel. Den første SQL-en for en unik argumentkombinasjon går uansett til MySQL. Mønstrene under dekker det query-cachen ikke tar seg av.
Transient rundt ID-lister
Har du en sammensatt spørring (for eksempel en lokal liste over “populære stillinger”), lagre ID-ene i en transient i en time. Ikke lagre hele WP_Query-objektet: det serialiserer hvert innlegg og hele spørringstilstanden, og den cachede kopien oppfører seg ikke lenger som en fersk spørring.
$cache_key = 'home_trending_jobs_v1';
$ids = get_transient( $cache_key );
if ( false === $ids ) {
$q = new WP_Query( [
/* Complex Args */
'fields' => 'ids',
'no_found_rows' => true,
] );
$ids = $q->posts;
set_transient( $cache_key, $ids, HOUR_IN_SECONDS );
}
$trending = new WP_Query( [
'post__in' => $ids ?: [ 0 ],
'orderby' => 'post__in',
'no_found_rows' => true,
] );Slett transienten på save_post for posttypen den lister opp. Husk orderby => 'post__in' ved rehydrering, ellers mister du rekkefølgen. Ikke cache hele HTML-en i samme transient som ID-listen hvis innholdet er personalisert eller har nonce-avhengige deler.
Uten persistent objekt-cache lagres transients i wp_options. Hold antall nøkler nede og rydd utløpte transients. Med Redis: bruk et nøkkelprefiks per miljø (prod:, staging:) slik at cache ikke lekker mellom miljøer.
Slå av cache-fylling for én spørring
Etter at WP_Query har hentet innleggs-ID-ene, kaller den update_postmeta_cache() og update_object_term_cache() for å laste hver meta-rad og hver term-rad for disse innleggene inn i objekt-cachen. Det er riktig standard for en vanlig malloop som straks skal kalle get_post_meta() og get_the_terms(). For en loop som bare viser tittel og permalenke, er det bortkastet.
$args = [
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'no_found_rows' => true,
'fields' => 'ids',
];For et arkiv med 50 innlegg der hvert innlegg har 30 meta-rader, slipper du at 1 500 rader leses og caches i én forespørsel. Sammen med 'fields' => 'ids' slipper du å hydrere WP_Post-objekter i det hele tatt. Det er nyttig for sitemap-generatorer, ID-lister for relaterte innlegg i sidebar og widgets i adminpanelet.
Full page cache (nginx FastCGI cache, Cloudflare eller plugin-basert HTML-cache) sitter foran PHP-FPM. En billig WP_Query hjelper fortsatt ved cache miss, forhåndsvisning, innloggede brukere og WooCommerce-handlekurvsider der HTML ikke caches. Optimaliser spørringen uansett; stol ikke på edge-cache alene.
Når WP_Query deler spørringen i ID-er og objekter
Kjernen henter ikke alltid fulle rader i én SELECT. I WP_Query::get_posts(), når SQL-en ikke er endret av posts_*-filtre og enten en ekstern objekt-cache er aktiv eller spørringen har LIMIT med posts_per_page under 500, henter WordPress først bare ID-er og fyller deretter innleggsobjektene, fra objekt-cachen der de allerede ligger. Filteret split_the_query kan slå dette av eller på.
To konsekvenser for feilsøking:
- SQL-en i Query Monitor kan være
SELECT wp_posts.ID ...etterfulgt av en egen henting av innlegg som mangler. Det er kjerneoppførsel, ikke en plugin-feil. - En plugin som endrer SQL-en via
posts_*-filtrene for klausuler, slår av oppdelingen. En plugin som endrer feltene som velges (posts_fields,posts_fields_request,posts_clauses_request), slår også av query-cachen fra 6.1 for den forespørselen.
Det finnes også en full kortslutning: filteret posts_pre_query kjører før databasekallet, og hvis det returnerer et array, hopper WordPress over SQL-en helt. Søkeplugins bruker dette til å besvare WP_Query fra en ekstern indeks. Viser Query Monitor en WP_Query med resultater og ingen tilhørende SQL, se etter en posts_pre_query-callback.
Når bruke get_posts i stedet for WP_Query
get_posts() er en tynn wrapper rundt WP_Query som returnerer et array av WP_Post-objekter. Kjernen setter alltid 'no_found_rows' => true og 'ignore_sticky_posts' => true, og har 'suppress_filters' => true som standard. Det siste slår av SQL-filtrene posts_* (posts_where, posts_join, posts_clauses, the_posts osv.), men ikke pre_get_posts: den hooken kjører alltid, også for get_posts(). Bruk den når:
- du trenger et array av innlegg eller ID-er,
- du ikke trenger
have_posts()-loopen, - du ikke paginerer.
$ids = get_posts(
[
'post_type' => 'post',
'posts_per_page' => 5,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
]
);Velg new WP_Query() når du trenger found_posts, max_num_pages, egen paginering, eller at posts_*-filtrene skal kjøre. Velg pre_get_posts når du endrer det arkivet brukeren allerede er på. Velg get_posts() for korte, ikke-paginerte hjelpelister i PHP.
REST API (/wp/v2/posts) og blokkeditorens Query Loop er egne stier. De ender også i WP_Query, så callbackene dine på pre_get_posts må respektere is_admin(), REST-kontekst og $query->is_main_query() slik at du ikke endrer API-svar utilsiktet.
WP_Query er synkron i forespørselen. Unngå å kjøre tunge sekundære loops i wp_footer “fordi det er praktisk”. Flytt dem dit i malen der HTML-en faktisk trengs, eller last via REST/Ajax hvis blokken er under folden.
Slik endrer du WP_Query-resultater i REST API
Bygger du en React-frontend med headless WordPress, bruker du ikke WP_Query direkte i JavaScript, men REST API bruker den på backend.
Med filteret rest_{post_type}_query kan du endre hvordan API-et spør databasen ut fra URL-parametere. Sjekk endepunktet først: /wp/v2/posts tar allerede imot en innebygd exclude-parameter, så en egen parameter gir bare mening for logikk kjernen ikke dekker.
add_filter( 'rest_post_query', function( $args, $request ) {
$exclude = $request->get_param( 'exclude_ids' );
if ( ! empty( $exclude ) ) {
$args['post__not_in'] = wp_parse_id_list( $exclude );
}
return $args;
}, 10, 2 );wp_parse_id_list() gjør en kommaseparert streng om til et array av positive heltall, så en manipulert parameter kan ikke smugle noe annet inn i spørringen.
Sjekkliste for WP_Query-kode før merge
- Er dette hovedspørringen? Bruk
pre_get_posts, ikke en ekstra loop ellerquery_posts(). - Guard hver
pre_get_posts-callback medis_admin()og$query->is_main_query(). - Trenger du paginering? Hvis nei:
no_found_rows => true. Hvis ja: korrektpaged/page, og ikkeoffsetsammen medpaged. - Erstatt
posts_per_page => -1med et fast tak, eller hent i omganger medpaged. - Trenger du bare ID-er eller tittel og lenke?
fields => 'ids'ogupdate_post_meta_cache => false. - Er filteret en taksonomi, ikke meta? Filtrerer
meta_querylikevel på enmeta_key, leggINDEX (meta_key(32), post_id)påwp_postmeta. - Kall
wp_reset_postdata()etter hver sekundær loop som brukerthe_post()ellersetup_postdata(). - Kjør Query Monitor på den trege URL-en, sorter på tid, og verifiser at antall SQL ikke dobles på forsiden.
- Kjører samme
WP_Querypå hver sidevisning og dataene endres sjelden: cache ID-ene i en transient med nøkkel basert på argumentene.
Videre lesning internt: hvordan gjøre en WordPress-basert nettside raskere. Eksternt: den offisielle WP_Query-referansen, pre_get_posts og advanced administration: performance.
Rettelser på spørringsnivå er som regel den billigste måten å hente inn igjen TTFB-budsjettet på: billigere enn å oppgradere hostingpakken, billigere enn å legge til et CDN, og langt billigere enn å bygge nettstedet om til headless.







