WP_Query og The Loop: ytelse, taksonomier og maler

WP_Query og The Loop: ytelse, taksonomier og maler

Sist verifisert: 21. september 2026
9 min lesetid
Guide
Full-stack-utvikler
Core Web Vitals

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å norske hostinger med PHP-FPM (typisk Domene.no, Domeneshop, eller egen VPS med nginx + 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 hovedspørring versus sekundær WP_Query, pre_get_posts, ytelsesflagg, tax_query-feller, paginering, wp_reset_postdata(), Query Monitor, objekt-cache og når get_posts() er nok.

Offisiell referanse: WP_Query, pre_get_posts og ytelsesoptimalisering.

#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 overskriver hovedspørringen midt i forespørselen, ødelegger paginering og betingelser, og tvinger en ekstra databasehit. 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

pre_get_posts kjører før SQL bygges. Du får ett treff i databasen i stedet for «hent alt, deretter filtrer i PHP» eller «kjør en ny WP_Query ovenpå».

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' );
		}
	}
);

Tre regler som sparer timer med feilsøking:

  1. Guard med is_admin() og $query->is_main_query(). Uten dem treffer du REST, feeds, adminlister og hver sekundære loop.
  2. Sett argumenter med $query->set(), ikke ved å erstatte hele objektet.
  3. Ikke kall get_posts() eller new WP_Query() inne i callbacken uten streng begrunnelse. Det lager nye spørringer under byggingen av den du prøver å justere.

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.

#Raskere WP_Query med no_found_rows og fields ids

Standard WP_Query kjører en spørring som også beregner totalt antall treff for paginering (FOUND_ROWS() / historisk SQL_CALC_FOUND_ROWS). 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 over wire, mindre hydrering av WP_Post-objekter, og du kan fortsatt bruke ID-ene i post__in.

update_post_meta_cache og update_post_term_cache er nyttige 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 cache-warm.

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.

#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-kostnad. Flere relation => 'OR'-klausuler på store taksonomier gir brede joins. Preferer é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 ved omskriving av slug. I kode som lever lenge, lagre term-ID i config eller options.

Tomme arrays. En tom terms-liste kan gi «ingen treff» eller uventet SQL avhengig av versjon og operator. Valider input før du bygger spørringen.

meta_query for filtre som burde vært taksonomi. Meta er lagret som EAV (wp_postmeta). Indeksene er svakere enn term-relasjoner. Hvis feltet er et filter (bransje, by, produktlinje), registrer en custom taxonomy i stedet for å søke på meta_key.

$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.

#Paginering: paged versus page

WordPress bruker to query vars:

  • paged for arkiver, bloggindekser og de fleste tilpassede lister.
  • page for paginering inne i et statisk forsideinnlegg / sideinnhold (<!--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()) leser hovedspørringen ofte page, ikke paged. Mange themes feiler her: lenkene ser riktige ut, men loop nummer to starter alltid på side 1. 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.

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. Etter loopen må du kalle wp_reset_postdata() slik at resten av malen (sidebar, footer, related widgets) fortsatt ser hovedinnlegget.

$q = new WP_Query( $args );
while ( $q->have_posts() ) {
	$q->the_post();
	// mal
}
wp_reset_postdata();

wp_reset_query() er for query_posts()-æraen. Bruk den ikke i ny kode. Hvis du itererer over et array av WP_Post uten the_post(), bruk setup_postdata( $post ) inne i loopen og wp_reset_postdata() etterpå, eller unngå globale template-tagger og les $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

Query Monitor viser SQL, kallstack og hvilken komponent som eier spørringen. Arbeidsflyt som fungerer på staging:

  1. Åpne siden som føles treg (arkiv, produktliste, forside med tre «featured»-blokker).
  2. Se fanen Queries: sorter på tid. Finn WP_Query-rader med høy varighet eller mange like spørringer.
  3. Sjekk Duplicate Queries: ofte er det samme tax_query i header, hero og hovedliste.
  4. Se Hooks: bekreft at pre_get_posts-callbacken din faktisk kjører på hovedspørringen.

På delt hosting i Norge har du ofte begrenset memory_limit og få PHP-FPM-workere. Ti ekstra WP_Query på forsiden multipliseres under kampanjetrafikk. Query Monitor på lokal eller staging gir tallene; i produksjon holder det med New Relic / Blackfire hvis dere har det, eller midlertidig QM bak IP-restriksjon.

#Objekt-cache og WP_Query

WP_Query cacher ikke hele resultatmengden automatisk på tvers av forespørsler. Den varmer post-, meta- og term-cacher for ID-ene den henter. Med Redis eller Memcached som objekt-cache (vanlig på managed WordPress og egen VPS) blir gjentatte get_post() / meta-oppslag billigere, men den første SQL-en for en unik argumentkombinasjon går fortsatt til MySQL.

Praktiske mønstre:

  • Transient rundt ID-lister. Cache arrayet av ID-er fra en dyr tax_query, TTL 5-15 minutter, invalidér ved save_post for den posttypen.
  • Ikke cache hele HTML-en i samme transient som ID-listen hvis innholdet er personalisert eller har nonce-avhengige deler.
  • Sticky posts og post__in-rekkefølge. Når du cacher ID-er, husk orderby => 'post__in' ved rehydrering.

Uten objekt-cache lagres transients i wp_options (autoload kan bli et problem). På norske budsjethostinger uten Redis: korte TTL-er, få nøkler, og rydd døde transients. Med Redis: bruk en nøkkelprefiks per miljø (prod:, staging:) slik at cache ikke lekker mellom deploy-slots.

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, preview, innloggede brukere og WooCommerce-handlekurv-sider der HTML ikke caches. Optimaliser spørringen uansett; stol ikke på edge-cache alene.

#Når bruke get_posts i stedet for WP_Query

get_posts() er en tynn wrapper rundt WP_Query med noen standardverdier (suppress_filters historisk, no_found_rows-lignende oppførsel for enkle kall). 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 eksplisitt kontroll over filtre. Velg pre_get_posts når du endrer det arkivet brukeren allerede er på. Velg get_posts() for små, ikke-paginerte hjelpelister i PHP.

REST API (/wp/v2/posts) og blokkeditorens Query Loop er egne stier. De ender også i WP_Query, men filtrene dine på pre_get_posts må respektere is_admin(), REST-kontekst og WP_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 til stedet i malen der HTML faktisk trengs, eller last via REST/Ajax hvis blokken er under folden.

#Sjekkliste for WP_Query-kode før merge

  1. Er dette hovedspørringen? Bruk pre_get_posts, ikke en ekstra loop.
  2. Trenger du paginering? Hvis nei: no_found_rows => true. Hvis ja: korrekt paged / page.
  3. Trenger du bare ID-er? fields => 'ids'.
  4. Er filteret en taksonomi, ikke meta?
  5. Kall wp_reset_postdata() etter hver sekundær loop som bruker the_post().
  6. Verifiser med Query Monitor at antall SQL ikke dobles på forsiden.
  7. Med objekt-cache: cache ID-lister, ikke antagelser om «WordPress cacher spørringen for meg».

Videre lesning internt: hvordan gjøre en WordPress-basert nettside raskere. Eksternt: den offisielle WP_Query-referansen, pre_get_posts og advanced administration: performance.

Neste steg

Gjør artikkelen om til faktisk implementering

Denne blokken styrker intern lenking og sender leseren videre til de mest relevante tjenestene og innholdet.

Vil du få dette implementert på nettstedet ditt?

Hvis problemet er Core Web Vitals, treg rendering eller tung WordPress-kjoring, kan jeg definere og gjennomfore optimaliseringen.

Artikkel-FAQ

Ofte stilte spørsmål

Praktiske svar for å bruke temaet i faktisk arbeid.

SEO-readyGEO-readyAEO-ready1 Q&A
Hva er Den definitive guiden til wp_Query & the loop (2026-utgave)?#
Artikkelen dekker et konkret WordPress- eller frontendproblem og viser hva som bør sjekkes før endringen rulles ut.

Trenger du FAQ tilpasset bransje og marked? Vi lager en versjon som støtter dine forretningsmål.

Ta kontakt

Relaterte artikler