WordPress har levert innhold som XML i mange år, lenge før noen fant opp “headless CMS”. En RSS-feed er fortsatt den enkleste kontrakten: en liste med elementer, tittel, innhold eller utdrag, dato og kanonisk lenke. Aggregatorer, importverktøy for nyhetsbrev, feedlesere og podcastkataloger bruker den fortsatt.
Denne teksten er for utvikleren som har et tema eller en liten utvidelse og vil ha kontroll over feedene uten en “RSS everything”-pakke. Den dekker standardendepunktene, hva RSS2-malen faktisk sender sammenlignet med spesifikasjonen, add_feed, cache, trygg deaktivering, oppregning av brukere via forfatterfeeden, podcaster, JSON Feed og WebSub. Avsnittene om hvordan core oppfører seg er kontrollert mot kildekoden til WordPress (wp-includes/feed-rss2.php, feed-atom.php, feed.php, class-wp.php).
Hvilke RSS-feeder har WordPress som standard
Core registrerer flere typer feeder. Med pene permalenker slått på ser du som regel:
/feed/eller/feed/rss2/: RSS 2.0 (standard)/feed/atom/: Atom/feed/rdf/: RDF/RSS 1.0/comments/feed/: alle kommentarer/category/slug/feed/,/tag/slug/feed/,/author/login/feed/,/search/frase/feed/: kontekstfeeder
Uten pene permalenker går de samme dataene ut via query string (?feed=rss2). Funksjonen do_feed() leser variabelen feed og utløser handlingen do_feed_{navn}; standardhåndtererne laster filene wp-includes/feed-*.php. Finnes det ingen handling for navnet, avslutter WordPress forespørselen med 404.
I produksjon er det tre ting som teller:
- Discovery: core skriver ut
<link rel="alternate" type="application/rss+xml" ...>iwp_headvia funksjonenefeed_linksogfeed_links_extra, så sant temaet deklarereradd_theme_support( 'automatic-feed-links' ). Feedlesere og crawlere tar det de ser i HTML, ikke det som “burde vært der”. - Spørringens omfang: hovedfeeden henter innlegg (
post). CPT-er, sider og egne statuser kommer ikke med før du endrerpre_get_posts. - Fulltekst eller utdrag: innstillingen
rss_use_excerpt(Innstillinger, Lesing) bestemmer formen på elementene. RSS2-malen skriver alltid ut<description>med utdraget (the_excerpt_rss()), og ved fulltekst legger den til<content:encoded>med innholdet fraget_the_content_feed(). Filtrenethe_content_feedogthe_excerpt_rssvirker på disse to feltene.
Bygger du en importør for nyhetsbrev, sjekk først /feed/ uten utvidelser. Ofte er ikke problemet “mangler RSS”, men manglende miniatyrbilde, manglende CPT eller et for aggressivt CDN som cacher XML som om det var HTML.
XML-lagets hooks som er verdt å kjenne før du skriver en egen mal: rss2_ns (navnerom i rotelementet), rss2_head (barn av <channel>), rss2_item (barn av hvert <item>), samt Atom-motstykkene (atom_ns, atom_head, atom_entry). Innholdsfiltrene (the_content_feed, the_excerpt_rss) slår inn tidligere, på HTML-strengen som limes inn i elementet. Rekkefølgen betyr noe: først bygger du innholdet i elementet, deretter legger du XML-elementer rundt det.
Antall elementer i hovedfeeden styres av innstillingen posts_per_rss (Innstillinger, Lesing): WP_Query bruker den som posts_per_page for feedspørringer, og den samme grensen kutter kommentarfeeden. Ikke skru den opp til flere hundre “for sikkerhets skyld”: tung XML med fulltekst og shortcodes dreper TTFB og sprenger PHP-minnegrensene på delt webhotell.
Hva RSS2-malen i WordPress sender ifølge RSS 2.0-spesifikasjonen
RSS 2.0-spesifikasjonen krever tre elementer i kanalen: title, link og description. I et element er alle underelementer valgfrie, men minst ett av title eller description må være med. Datoer følger formatet fra RFC 822. Malen feed-rss2.php oppfyller dette minimumet og legger til noen elementer som er verdt å kjenne til før du begynner å duplisere dem i hooks:
<atom:link rel="self">med adressen til selve feeden (Atom-navnerommet er deklarert i RSS2-roten),<lastBuildDate>og<language>fra nettstedets innstillinger,<sy:updatePeriod>og<sy:updateFrequency>fra Syndication-modulen, som standardhourlyog1, endret med filtrenerss_update_periodogrss_update_frequency,- i hvert element
<dc:creator>, kategorier,<pubDate>i sonen+0000og<guid isPermaLink="false">.
Elementet <ttl> (antall minutter kanalen kan caches) finnes ikke i core-malen. Respekterer leseren eller aggregatoren din det, legger du det til på rss2_head.
guid fortjener egen oppmerksomhet. Spesifikasjonen beskriver det som en streng som entydig identifiserer elementet; isPermaLink er true som standard, og ved false kan ikke leseren anta at det er en URL. WordPress skriver ut innleggets guid-felt, fastsatt ved opprettelse (vanligvis på formen https://domene/?p=123), og merker det isPermaLink="false". Det er et riktig valg: endret slug eller domene endrer ikke identifikatoren, så feedlesere viser ikke gamle innlegg som nye. Ikke overskriv guid ved domeneflytting med mindre du vil at abonnentene skal få hele arkivet i reprise.
Spesifikasjonen har også <enclosure> med tre obligatoriske attributter: url, length (størrelse i byte) og type (MIME-type). RSS2-malen kaller rss_enclosure(), som leser innleggets egendefinerte felt enclosure (URL, lengde og type på hver sin linje) og skriver ut et ferdig element. Før du legger til din egen enclosure i rss2_item, sjekk at innlegget ikke allerede har dette feltet, ellers får du et duplikat.
Hvordan Atom i WordPress skiller seg fra RSS 2.0
Atom er beskrevet i RFC 4287 fra 2005 og er strengere enn RSS. Elementet atom:feed må inneholde nøyaktig én atom:id, én atom:title og én atom:updated, og i tillegg minst én atom:author, med mindre hver oppføring har sin egen forfatter. De samme tre elementene er obligatoriske i hver atom:entry. Feeden bør ha atom:link med rel="self", datoer må følge formatet date-time fra RFC 3339, og MIME-typen er application/atom+xml.
Malen feed-atom.php løser det slik: kanalens <id> er adressen til Atom-feeden, oppføringens <id> er guid, hver oppføring har <author>, og ved siden av <published> skriver den ut <updated> med datoen for siste endring av innlegget. Det er en praktisk forskjell: RSS2 fra core har bare pubDate, så en rettelse i en publisert tekst etterlater ikke spor der, mens Atom signaliserer den via updated. Synkroniserer mottakerne dine endringer i innholdet (arkiver, interne søkemotorer, importverktøy), gi dem /feed/atom/.
Hvordan legge til miniatyrbilder og media i RSS-feeden
Standardfeeden legger ikke fremhevet bilde inn i innholdet. De fleste nyhetsbrevverktøy leter etter <img> i elementets innhold eller i Media RSS. En minimal og forutsigbar løsning:
declare(strict_types=1);
function wppoland_feed_prepend_thumbnail( string $content ): string {
if ( ! is_feed() || ! has_post_thumbnail() ) {
return $content;
}
$post_id = get_the_ID();
$url = get_the_post_thumbnail_url( $post_id, 'medium_large' );
if ( ! $url ) {
return $content;
}
$img = sprintf(
'<p><img src="%s" alt="%s" /></p>',
esc_url( $url ),
esc_attr( get_the_title( $post_id ) )
);
return $img . $content;
}
add_filter( 'the_content_feed', 'wppoland_feed_prepend_thumbnail' );
add_filter( 'the_excerpt_rss', 'wppoland_feed_prepend_thumbnail' );Med fulltekst i feeden havner begge filtrene i samme element (utdraget i <description>, innholdet i <content:encoded>), så bildet dukker opp i begge feltene. De fleste feedlesere viser content:encoded, mens importverktøy for nyhetsbrev ofte leser description, og derfor lønner det seg å beholde begge filtrene.
For lesere som respekterer Media RSS kan du også legge til navnerommet xmlns:media="http://search.yahoo.com/mrss/" på rss2_ns og elementet media:content på rss2_item:
declare(strict_types=1);
function wppoland_media_ns(): void {
echo 'xmlns:media="http://search.yahoo.com/mrss/"' . "\n";
}
add_action( 'rss2_ns', 'wppoland_media_ns' );
function wppoland_media_content(): void {
$thumb_id = get_post_thumbnail_id();
$src = $thumb_id ? wp_get_attachment_image_src( $thumb_id, 'large' ) : false;
if ( ! $src ) {
return;
}
printf(
'<media:content url="%s" medium="image" type="%s" width="%d" height="%d" />' . "\n",
esc_url( $src[0] ),
esc_attr( (string) get_post_mime_type( $thumb_id ) ),
(int) $src[1],
(int) $src[2]
);
}
add_action( 'rss2_item', 'wppoland_media_content' );Ikke bland de to tilnærmingene uten grunn: én kanal, én konvensjon, færre overraskelser i importverktøyene.
Forsinket publisering i feeden
Scrapere og aggregatorer henter /feed/ sekunder etter publisering. Indekseres originalen tregere enn kopien, har du den klassiske striden om kanonisitet. Å forsinke elementene i feedspørringen med en time gir crawleren på ditt eget domene et forsprang:
declare(strict_types=1);
function wppoland_delay_feed_posts( string $where, WP_Query $query ): string {
global $wpdb;
if ( ! $query->is_main_query() || ! $query->is_feed() || $query->is_comment_feed() ) {
return $where;
}
$where .= $wpdb->prepare(
" AND {$wpdb->posts}.post_date_gmt <= %s ",
gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS )
);
return $where;
}
add_filter( 'posts_where', 'wppoland_delay_feed_posts', 10, 2 );Filteret posts_where sender spørringsobjektet som andre argument, så betingelsen gjelder bare hovedspørringen for feeden, ikke widgets eller blokker som rendres i samme forespørsel. Å sammenligne kolonnen med en ferdig dato (i stedet for TIMESTAMPDIFF på kolonnen) lar databasen bruke indeksen på post_date_gmt.
Dette erstatter ikke rel=canonical eller nettstedskartet. Det er bare en tidsbuffer i XML-laget. På nettsteder med hyppige rettelser etter publisering bør du sette en lengre terskel eller utelukke kladdestatuser på en annen måte.
Egendefinerte innleggstyper i hovedfeeden
Hoved-/feed/ leser typen post. Portefølje, arrangementer eller release notes står utenfor listen til du utvider spørringen:
declare(strict_types=1);
function wppoland_include_cpt_in_main_feed( WP_Query $query ): void {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_feed() ) {
return;
}
// Bare hovedfeeden, ikke /author/.../feed/ eller kommentarer.
if ( $query->is_comment_feed() || $query->is_author() || $query->is_category() || $query->is_tag() || $query->is_search() ) {
return;
}
$query->set(
'post_type',
array( 'post', 'portfolio', 'event' )
);
}
add_action( 'pre_get_posts', 'wppoland_include_cpt_in_main_feed' );Betingelsen is_main_query() er avgjørende: uten den endrer du hver spørring som kjøres mens feeden genereres, også spørringene fra utvidelser. En egen CPT-feed (/feed/portfolio/) er ofte ryddigere enn å blande typer i én XML. En innholdstype registrert med has_archive har også sin egen feed under arkivadressen, for eksempel /portfolio/feed/.
Hvordan lage en egen RSS-feed med add_feed
add_feed( $feedname, $callback ) registrerer en rewrite for /feed/{navn}/ og kaller callbacken i stedet for den vanlige RSS2-malen. Det er det offisielle API-et fra feeds-dokumentasjonen og referansen for add_feed. Callbacken får to argumenter: om det er en kommentarfeed, og navnet på feeden.
Et produksjonsmønster:
declare(strict_types=1);
function wppoland_register_release_feed(): void {
add_feed(
'releases',
static function (): void {
header( 'Content-Type: application/rss+xml; charset=' . get_option( 'blog_charset' ), true );
echo '<?xml version="1.0" encoding="' . esc_attr( get_option( 'blog_charset' ) ) . '"?' . '>';
// Her bygger du et komplett RSS2-dokument eller laster en egen mal.
load_template( get_stylesheet_directory() . '/feed-releases.php' );
}
);
}
add_action( 'init', 'wppoland_register_release_feed' );Etter første registrering (eller når feeden får nytt navn) kjører du flush_rewrite_rules() én gang, ved aktivering av utvidelsen, ikke på hver init. Uten flush får du 404 på /feed/releases/, selv om callback-koden er riktig.
I callbacken:
- sett
Content-Typefør du skriver ut en eneste byte XML, - escape hvert brukerfelt (
esc_xml/esc_url/esc_html), - begrens spørringen (
posts_per_page, statuspublish, eventuelt meta query), - ikke kall tunge shortcodes uten cache.
Headeren i callbacken trengs av en konkret grunn. For hver feedforespørsel setter WP::send_headers() Content-Type fra funksjonen feed_content_type(), som bare kjenner rss, rss2, rss-http, atom og rdf. For et ukjent navn returnerer den application/octet-stream. Enten overstyrer du headeren i callbacken (andre argument true), eller så legger du til typen med filteret feed_content_type, som får MIME-typen og navnet på feeden.
Trenger du bare en variant av hoved-RSS2 (et annet antall elementer, en annen type), holder det noen ganger med add_feed + pre_get_posts på is_feed( 'releases' ) og standardmalen. En egen malfil lager du når XML-en har andre navnerom eller en annen elementstruktur.
Navnet på feeden havner i rewrite-taggen feed. Vær forsiktig med navn fra core (rss2, atom, rdf, rss): add_feed fjerner standardhåndtereren do_feed_{navn} og kobler på din callback, så add_feed( 'rss2', ... ) bytter stille ut hovedfeeden for hele nettstedet. Etter et navnebytte slutter den gamle URL-en å virke til du legger inn en omdirigering i serverkonfigurasjonen eller i template_redirect. Under lokal testing etter add_feed: gå til Innstillinger, Permalenker og lagre uten endringer hvis du ikke har flush ved aktivering av utvidelsen. Det er samme mekanisme.
Hvordan feed-cache fungerer i WordPress
Her finnes tre ulike mekanismer som er lette å blande: cache for eksterne feeder som WordPress henter, betingede svar for dine egne feeder og cache på CDN.
Eksterne feeder. fetch_feed() bygger et SimplePie-objekt fra en RSS- eller Atom-adresse og returnerer det, og ved feil returnerer den WP_Error. Den tar også imot en liste med adresser og slår dem sammen til én kanal. Resultatet lagres i transients, og levetiden endres med filteret wp_feed_cache_transient_lifetime (standard 12 timer). Dette er ikke cachen for din egen /feed/.
declare(strict_types=1);
$rss = fetch_feed( 'https://example.com/feed/' );
if ( ! is_wp_error( $rss ) ) {
$max = $rss->get_item_quantity( 5 );
$items = $rss->get_items( 0, $max );
foreach ( $items as $item ) {
printf(
'<li><a href="%s">%s</a></li>',
esc_url( $item->get_permalink() ),
esc_html( $item->get_title() )
);
}
}
add_filter(
'wp_feed_cache_transient_lifetime',
static function (): int {
return HOUR_IN_SECONDS;
}
);Sjekken is_wp_error() er ingen formalitet: en utilgjengelig kildeserver eller ugyldig XML er hverdagskost, og uten denne betingelsen kaster widgeten en fatal error på forsiden. Innhold fra SimplePie går gjennom en sanitizer basert på KSES, men titler og lenker escaper du likevel når du skriver dem ut.
Dine egne feeder og 304-svar. Core håndterer betingede forespørsler for feeder uten noen utvidelse. WP::send_headers() beregner ved hver forespørsel med variabelen feed satt en Last-Modified ut fra datoen for siste endring av et innlegg (for kommentarfeeder også av en kommentar), lager en ETag av den, og når klienten sender en samsvarende If-Modified-Since eller If-None-Match, svarer den 304 og avslutter forespørselen. Det gjelder også endepunkter fra add_feed, fordi de går gjennom den samme variabelen. Egen 304-logikk skriver du bare for endepunkter utenfor denne mekanismen, for eksempel i REST API.
Eget endepunkt /feed/ eller /feed/releases/ på kanten:
- CDN / reverse proxy: sett
Cache-Controlbevisst. En offentlig feed medmax-age=300holder som regel. Etter publisering invaliderer du stien/feed/eller versjonerer ETag. - Object cache: bygger du tung JSON eller kombinerer CPT med meta, cache svarstrengen i
wp_cache_setmed kort TTL og tøm den påtransition_post_status/save_post. - Ikke cache HTML per bruker: feeden er offentlig; personalisering i XML ender med datalekkasje mellom abonnenter.
- Sidecache fra utvidelser: noen fullstendige HTML-cacher serverer
/feed/som en side med feilContent-Type. Utelatfeed-stier fra cachereglene eller tving bypass foris_feed().
Eksempel på en kort cache-header i en egen feed:
header( 'Cache-Control: public, max-age=300' );
header( 'X-Robots-Tag: noindex' ); // valgfritt, hvis du ikke vil indeksere XML-ennoindex på en feed slår ikke av syndikering. Det blokkerer bare indeksering av selve XML-en som “side”. Med Cloudflare eller en annen edge: en cacheregel for *.xml / path /feed* med kort TTL og Cache Everything bare når origin returnerer riktig MIME-type. En feil edge-cache kan holde på et tomt dokument i timevis etter en flush av rewrite-reglene.
Hvordan deaktivere eller begrense RSS-feeder trygt
Å slå av feedene “hardt” uten en plan etterlater hengende discovery-lenker og gamle bokmerker i feedlesere. En trygg rekkefølge:
- Fjern discovery-lenkene:
remove_action( 'wp_head', 'feed_links', 2 )ogremove_action( 'wp_head', 'feed_links_extra', 3 ), med prioritetene som core registrerer dem med. - Bestem om de gamle URL-ene skal gi 301 til HTML-innhold, en tom 410, eller fortsatt servere XML.
- Først deretter slår du av
do_feed_*-håndtererne.
Core registrerer fire håndterere: do_feed_rdf, do_feed_rss, do_feed_rss2 og do_feed_atom. Egne håndterere for kommentarer finnes ikke: /comments/feed/ går gjennom do_feed_rss2 (eller do_feed_atom) med første argument satt til true. Minimal avskjæring av de klassiske feedene (lar muligheten for egen add_feed stå åpen):
declare(strict_types=1);
function wppoland_disable_default_feeds(): void {
remove_action( 'do_feed_rdf', 'do_feed_rdf', 10 );
remove_action( 'do_feed_rss', 'do_feed_rss', 10 );
remove_action( 'do_feed_rss2', 'do_feed_rss2', 10 );
remove_action( 'do_feed_atom', 'do_feed_atom', 10 );
}
add_action( 'init', 'wppoland_disable_default_feeds' );
function wppoland_feed_gone(): void {
wp_die(
esc_html__( 'Denne feeden er deaktivert.', 'wppoland' ),
'',
array( 'response' => 410 )
);
}
add_action( 'do_feed_rss2', 'wppoland_feed_gone', 1 );
add_action( 'do_feed_atom', 'wppoland_feed_gone', 1 );RDF og RSS 0.92 uten håndterer ender med 404 fra do_feed(), mens RSS2 og Atom (sammen med kommentarvariantene sine) får 410.
Et mildere alternativ: behold RSS2, kutt Atom/RDF og kommentarfeeden. Aggregatorer bruker uansett RSS2. Kommentarfeeden alene slår du av ved å sjekke håndtererens argument:
declare(strict_types=1);
function wppoland_comments_feed_gone( bool $is_comment_feed ): void {
if ( $is_comment_feed ) {
wp_die( esc_html__( 'Kommentarfeeden er deaktivert.', 'wppoland' ), '', array( 'response' => 410 ) );
}
}
add_action( 'do_feed_rss2', 'wppoland_comments_feed_gone', 1 );
add_action( 'do_feed_atom', 'wppoland_comments_feed_gone', 1 );
add_filter( 'feed_links_show_comments_feed', '__return_false' );Filteret feed_links_show_comments_feed fjerner discovery-lenken til kommentarfeeden fra <head>. Ikke fjern rewrite-reglene for feeder uten flush, ellers får du 404 i stedet for et kontrollert svar.
Hvordan forfatterfeeden avslører WordPress-brukere
/author/admin/feed/ og /author/mariusz/feed/ bekrefter at et brukernavn finnes. Sammen med /?author=1 (omdirigering til arkivet) er dette den klassiske vektoren for å kartlegge kontoer før brute force eller målrettet phishing.
Hard stenging av forfatterarkivene (inkludert feedene):
declare(strict_types=1);
function wppoland_disable_author_archives(): void {
if ( is_author() ) {
global $wp_query;
$wp_query->set_404();
status_header( 404 );
nocache_headers();
}
}
add_action( 'template_redirect', 'wppoland_disable_author_archives' );Skal forfatterarkivene bli (personlig blogg), gjør i det minste dette:
- bruk et pent
nicename, ikke det rå brukernavnet i URL-en, hvis modellen din tillater det, - ikke publiser en forfatterliste med brukernavn i HTML,
- vurder å begrense forfatterfeeden til en tom liste eller 404 bare for
is_feed() && is_author().
Husk at RSS2-malen skriver ut <dc:creator> med forfatterens visningsnavn i hvert element, og Atom elementet <author>. Er visningsnavnet likt brukernavnet, avslører hovedfeeden det like mye som forfatterarkivet.
REST API (/wp-json/wp/v2/users) er et eget tema. Å slå av forfatterfeeden lukker ikke oppregning via REST uten ekstra filtre. Se på RSS som én av mange flater som avslører identitet.
Ikke legg e-postadresser, tokens, interne ordre-ID-er eller innhold med status private / draft i feeden. Innholdsfiltrene virker også i feedkontekst: en shortcode som viser et kundepanel i HTML, kan lekke til offentlige abonnenter i XML.
Kommentarfeeden (/comments/feed/ eller feeden under et enkelt innlegg) blir ofte hoppet over i revisjoner. Inneholder kommentarene påloggingsinformasjon, ordrenumre eller personopplysninger om B2B-kunder, slå av kommentarfeeden med koden fra forrige avsnitt eller hold moderering på før kommentaren havner i XML. En offentlig kommentarfeed er en egen flate for scraping, ikke bare “en bonus for leserne”.
Hvordan lage en podcastfeed med iTunes og enclosure
WordPress er ikke vert for lydfiler, men kan være en RSS-generator som katalogene godtar. Kravene til Apple Podcasts er tydelige: hver episode må ha en unik <enclosure> med tre deler (URL, lengde, type), hver episode må ha en GUID som aldri endres, og feeden må inneholde minst én episode. Listen over påkrevde kanaltagger har Apple i et eget dokument om RSS-tagger, og det er det du sammenligner feeden med før innsending.
Teknisk trenger du:
- navnerommet
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"(en historisk navnerom-URL; katalogene gjenkjenner den), - kanaltagger fra
itunes-navnerommet (for eksempelitunes:author,itunes:category,itunes:image) samtlanguage, - elementer med
<enclosure url="..." length="..." type="audio/mpeg" />og eventueltitunes:duration.
Hooks i core:
declare(strict_types=1);
function wppoland_podcast_ns(): void {
echo ' xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"';
}
add_action( 'rss2_ns', 'wppoland_podcast_ns' );
function wppoland_podcast_head(): void {
echo '<itunes:author>' . esc_xml( get_bloginfo( 'name' ) ) . '</itunes:author>';
}
add_action( 'rss2_head', 'wppoland_podcast_head' );
function wppoland_podcast_item_enclosure(): void {
$url = get_post_meta( get_the_ID(), 'episode_audio_url', true );
$len = (int) get_post_meta( get_the_ID(), 'episode_audio_length', true );
if ( ! is_string( $url ) || $url === '' || $len < 1 ) {
return;
}
printf(
'<enclosure url="%s" length="%d" type="audio/mpeg" />',
esc_url( $url ),
$len
);
}
add_action( 'rss2_item', 'wppoland_podcast_item_enclosure' );Koden forutsetter at episodene ikke har feltet enclosure utfylt, som rss_enclosure() skriver ut automatisk. Funksjonen esc_xml() har vært i core siden versjon 5.5 og er riktig escaping for tekst i XML-elementer.
I produksjon er en egen feed (add_feed( 'podcast', ... )) vanligvis bedre enn å blande vanlige innlegg med episoder. Katalogene validerer hele kanalen; ett dårlig element kan få innsendingen avvist. Ha MP3-filene på en stabil URL (CDN), med riktig length i byte, for gjettet lengde ødelegger for klientene. Apple anbefaler også å slå på HEAD-forespørsler og forespørsler med byteintervall på filserveren, siden strømming avhenger av dem.
Hvordan legge til JSON Feed i WordPress
JSON Feed er et alternativ til XML for JS-klienter. WordPress core registrerer det ikke som standard. Du legger det til med add_feed( 'json', $callback ) og headeren Content-Type: application/feed+json, fordi det er MIME-typen spesifikasjonen anbefaler.
Skjelett for callbacken:
declare(strict_types=1);
function wppoland_json_feed(): void {
header( 'Content-Type: application/feed+json; charset=utf-8', true );
$query = new WP_Query(
array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 20,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
)
);
$items = array();
foreach ( $query->posts as $post ) {
$items[] = array(
'id' => (string) get_the_guid( $post ),
'url' => get_permalink( $post ),
'title' => get_the_title( $post ),
'content_html' => apply_filters( 'the_content', $post->post_content ),
'date_published' => get_post_time( DATE_ATOM, true, $post ),
'date_modified' => get_post_modified_time( DATE_ATOM, true, $post ),
);
}
echo wp_json_encode(
array(
'version' => 'https://jsonfeed.org/version/1.1',
'title' => get_bloginfo( 'name' ),
'home_page_url' => home_url( '/' ),
'feed_url' => home_url( '/feed/json/' ),
'language' => get_bloginfo( 'language' ),
'items' => $items,
),
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);
}
add_action(
'init',
static function (): void {
add_feed( 'json', 'wppoland_json_feed' );
}
);JSON Feed erstatter ikke RSS for podcaster eller i eldre feedlesere. Se på det som en ekstra kontrakt for egne klienter og interne verktøy. Discovery i HTML: type="application/feed+json".
Spesifikasjonen krever feltene version, title og items på kanalnivå, og i hvert element id (streng) og minst ett av content_html eller content_text. Versjon 1.1 la til feltet authors (en liste, flere forfattere per kanal eller element) og feltet language, og merket det enkle author som utdatert. Hold elementets id stabil: spesifikasjonen sier at id ikke bør endres når elementet oppdateres, og at nye elementer ikke bør bruke en id som er brukt før. Derfor er id i eksemplet guid, ikke permalenken, som endres sammen med slugen. content_text eller content_html: velg ett som sannhetskilde; å sende begge med innhold som spriker gir inkonsekvent forhåndsvisning i klientene.
Hvordan slå på WebSub for en WordPress-feed
En feedleser uten WebSub spør feeden i sin egen takt, så et nytt innlegg når abonnentene med en forsinkelse som avhenger av leseren. WebSub, en W3C-anbefaling, snur retningen: utgiveren peker på en hub, abonnentene registrerer seg hos huben, og huben sender ut oppdateringen når utgiveren melder fra om en endring.
Kravene på utgitersiden er korte. Spesifikasjonen anbefaler en Link-header med minst én rel="hub" og nøyaktig én rel="self", og for XML-feeder innebygde link-elementer. Utgiveren må varsle de oppgitte hubene om oppdateringer av emnet, men selve varslingsmekanismen lar spesifikasjonen stå åpen. I en merknad gir den et eksempel som brukes av offentlige huber: en POST-forespørsel med nøklene hub.mode=publish og hub.url som peker på den endrede ressursen.
WordPress core implementerer ikke WebSub. I utvidelseskatalogen finnes utvidelsen “WebSub (FKA. PubSubHubbub)”, som legger til begge elementene. Manuelt ser det slik ut:
declare(strict_types=1);
const WPPOLAND_HUB = 'https://hub.example.com/';
function wppoland_websub_links(): void {
printf( '<atom:link rel="hub" href="%s" />' . "\n", esc_url( WPPOLAND_HUB ) );
}
add_action( 'rss2_head', 'wppoland_websub_links' );
function wppoland_websub_ping( string $new_status, string $old_status, WP_Post $post ): void {
if ( 'publish' !== $new_status || 'publish' === $old_status || 'post' !== $post->post_type ) {
return;
}
wp_remote_post(
WPPOLAND_HUB,
array(
'body' => array(
'hub.mode' => 'publish',
'hub.url' => get_feed_link( 'rss2' ),
),
'blocking' => false,
)
);
}
add_action( 'transition_post_status', 'wppoland_websub_ping', 10, 3 );rel="self" finnes allerede i RSS2-malen (<atom:link rel="self">), så du legger bare til rel="hub". Sjekk hubens adresse og varslingsformat i dokumentasjonen til huben du velger, fordi spesifikasjonen ikke bestemmer dem. Verifisering av abonnementer (hub.challenge), utløp etter hub.lease_seconds og signaturen X-Hub-Signature ved bruk av hemmelighet håndteres av huben og abonnenten, ikke av WordPress.
Hvordan fikse XML-parsefeil i en WordPress-feed
Den vanligste feilen med egne maler og utvidelser er et dokument som leseren avviser med en melding av typen XML or text declaration not at start of entity. Deklarasjonen <?xml ... ?> må være det første tegnet i svaret. En tom linje eller en BOM skrevet ut foran den er nok til at parseren nekter å behandle hele kanalen.
Årsaken ligger nesten alltid i en PHP-fil som lastes ved hver forespørsel: et mellomrom eller linjeskift etter avsluttende ?> i functions.php eller i en utvidelse, eller en fil lagret som UTF-8 med BOM. Den riktige rettelsen er å fjerne avsluttende ?> fra filer som bare inneholder PHP, og lagre filene uten BOM. For å finne synderen kjører du curl -s https://domene/feed/ | head -c 100 | xxd og ser hva som står foran <?xml, og deretter slår du av utvidelsene én etter én.
Kan du ikke rette en annens utvidelse med en gang, hjelper en utdatabuffer som renser starten av feedsvaret midlertidig:
declare(strict_types=1);
function wppoland_clean_feed_output(): void {
if ( ! is_feed() ) {
return;
}
ob_start(
static function ( string $buffer ): string {
// Fjern BOM og blanktegn foran XML-deklarasjonen.
return ltrim( preg_replace( '/^\xEF\xBB\xBF/', '', $buffer ) );
}
);
}
add_action( 'template_redirect', 'wppoland_clean_feed_output', 1 );Dette er en lapp, ikke en reparasjon: bufferen fjerner ikke tegn som er skrevet ut før template_redirect kjører. Team med integrasjonstester kan passe på dette automatisk. Feedtestene i selve core laster malen direkte og demper advarsler om headere; samme mønster fungerer i WP_UnitTestCase:
class Feed_Rss2_Test extends WP_UnitTestCase {
public function test_rss2_is_well_formed(): void {
self::factory()->post->create_many( 3 );
$this->go_to( '/?feed=rss2' );
ob_start();
@require ABSPATH . WPINC . '/feed-rss2.php';
$xml = ob_get_clean();
$this->assertStringStartsWith( '<?xml', $xml );
$dom = new DOMDocument();
$this->assertTrue( $dom->loadXML( $xml ) );
}
}Assertionen sjekker råresultatet uten trim(), fordi det nettopp er blanktegn i starten som er feilen her.
Hvordan drive RSS-feeder i et flerspråklig WordPress
Med WPML, Polylang eller egne språkprefikser eksponerer WordPress vanligvis flere feedadresser. Typiske feil:
- hovedfeeden blander språkversjoner fordi
pre_get_postsikke filtrerer på språk, - CDN-et cacher
/feed/uten å skille på sti eller informasjonskapsel og gir innhold på ett språk til abonnenter på et annet, - adressene i
enclosurepeker på feil domene etter domenemapping, - elementet
<language>i kanalen kommer fra nettstedets innstillinger, ikke fra språket i innholdet i den aktuelle feeden.
Hold ett tydelig endepunkt per språk, filtrer på språk eller taksonomi tidlig i spørringen, og test feeden med en leser som faktisk sender If-None-Match, ikke bare med en nettleserfane. Med flere merkevarer i én installasjon er en egen add_feed( 'marka-a', ... ) ryddigere enn en spørringsparameter limt på hovedfeeden: parametere splitter cachen på CDN-siden, og ETag fra core avhenger bare av datoen for siste endring, ikke av parametere.
Hva du bør sjekke før du ruller ut en egen RSS-feed
Før du sender endringene til produksjon:
- Åpne
/feed/i nettleseren og medcurl -I: status 200, fornuftigContent-Type. - Etter
add_feed: aktiver utvidelsen / kjør flush av rewrite og sjekk den nye URL-en. - Fjern overflødige
rel=alternatehvis du slår av feeder. - Sjekk
/author/.../feed/med tanke på oppregning. - Lager du en podcast, kjør XML-en gjennom katalogens validator før innsending.
- Etter publisering av et testinnlegg: bekreft at CDN-et leverer fersk XML innen en akseptabel TTL.
- Kontroller at shortcodes og dynamiske blokker i feedinnholdet ikke slipper ut HTML fra adminpanelet eller sesjonsdata.
- Send en ny forespørsel med headeren
If-None-Matchsatt til den mottatteETag-verdien og sjekk at du får 304 tilbake.
RSS i WordPress er et API-lag, ikke et levn. Du kontrollerer spørringen, malen, cachen og angrepsflaten på samme måte som for et REST-endepunkt. Forskjellen er at feedklientene er eldre, mindre tilgivende overfor dårlig XML og mer tilbøyelige til å cache svaret på sin side.
Offisielle referansepunkter: Feeds (Developer Resources), add_feed og WordPress Feeds i administrasjonsdokumentasjonen. Før du skriver enda en “RSS Pack”-utvidelse, sjekk hvilken av disse tre kontraktene temaet ditt faktisk mangler.
Trenger du å utvide feeden med egne innholdstyper, metafelter eller en egen podcastkanal som del av WordPress-utvikling, start med én add_feed og én mal, uten en monolittisk “all feeds”-utvidelse.







