Ein Kunde rief mich Anfang 2023 an — ein Medienverleger mit etwa 14.000 Beiträgen auf WordPress, ein Theme, das über sechs Jahre von vier Entwicklern zusammengestückelt worden war, und ein Core Web Vitals Score, der wirklich peinlich war. Sein LCP lag bei 7,2 Sekunden auf Mobilgeräten. Sie hatten WP Rocket versucht, sie hatten ein CDN versucht, sie hatten sogar die Hälfte ihrer Plugins entfernt. Immer noch langsam. Das Problem war nicht WordPress selbst. Es war, dass jeder einzelne Seitenaufbau durch PHP, ein überladenes Theme und eine Datenbankabfragekette lief, die seit 2018 nicht mehr angeschaut worden war.
Das war der Moment, in dem ich mich richtig auf ein Headless-Setup mit Astro als Frontend festgelegt habe. Nicht, weil es modisch ist — sondern weil es für eine Website mit tausenden Beiträgen und umfangreichen redaktionellen Inhalten die einzige Architektur war, die Sinn machte.Astro as the frontend. Not because it's fashionable — because for a site pushing thousands of posts with heavy editorial content, it was the only architecture that made sense.
Hier ist genau, wie ich es aufgebaut habe. Die Kompromisse, die echte Konfiguration, die Bits, die mich gebissen haben.
---
Warum Astro und nicht Next.js
Ich bekomme diese Frage häufig gestellt. Next.js ist die offensichtliche Antwort, wenn du bereits ein React-lastiges Team hast oder komplexe Client-seitige Interaktivität brauchst. Aber für inhaltsreiche Seiten? Astro gewinnt, und das ist nicht besonders knapp.
Astro liefert standardmäßig null JavaScript aus. Für einen Blog, eine Nachrichtenseite oder ein Dokumentationsportal ist das die richtige Standardeinstellung. Du schaltest JavaScript dort ein, wo du es brauchst, anstatt dich von einem 200-KB-React-Bundle zu befreien, das du ohnehin größtenteils nicht willst. Die Astro-Dokumentation zur partiellen Hydration – sie nennen es die Islands-Architektur – erklärt es besser, als ich es in einem Satz kann, aber die Kurzversion ist: nur die interaktiven Teile bekommen JS. Der Artikeltext, der Header, die Seitenleiste? Statisches HTML.zero JavaScript by default. For a blog, a news site, or a documentation portal, that's the correct default. You opt into JavaScript where you need it, rather than opting out of a 200kb React bundle you mostly don't want. The Astro docs on partial hydration — they call it the Islands architecture — explain it better than I can in a sentence, but the short version is: only the interactive bits get JS. The article body, the header, the sidebar? Static HTML.
Ich habe Ende 2022 eine Rechtsinhalt-Website mit Next.js und WordPress gebaut. Schnell genug, aber der Kunde fragte ständig, warum sein Lighthouse-Score auf dem Mobilgerät 74 betrug, wenn „es jetzt schnell sein soll". Hydration-Overhead. Mit Astro erreicht dieselbe Art von Website jetzt routinemäßig 95–98. Keine Prahlerei – das ist einfach das, was die Architektur dir kostenlos gibt.
Was Astro nicht gut kann
Es ist ehrlich zu sein wert. Falls deine Website Echtzeit-Personalisierung braucht, einen umfangreichen Warenkorb oder irgendetwas, das wirklich von Client-seitigem State über viele Komponenten hinweg abhängt, wird sich Astro unangenehm anfühlen. Es ist keine React-App. Das Islands-Pattern ist mächtig, aber es ist ein anderes Denkmodell als das Bauen von SPAs. Ich habe versucht, ein Client-Dashboard Mitte 2023 in ein Astro-Projekt zu zwingen, und bin innerhalb von zwei Wochen zu Next.js zurückgekehrt. Wisse, was du baust.
---
WordPress als Headless CMS einrichten
WordPress ist ein genuinely gutes Headless-Backend. Die WP REST API ist im Core enthalten, sie ist gut dokumentiert, und dein Editorial-Team muss nichts Neues lernen. Dieser letzte Punkt ist wichtiger, als Entwickler normalerweise zugeben.WP REST API ships in core, it's well-documented, and your editorial team doesn't have to learn anything new. That last point matters more than developers usually admit.
Hier ist das Setup, das ich verwende:
- Installiere WordPress auf einer Subdomain – ich verwende cms.yourdomain.com oder api.yourdomain.com. Halte es hinter Basic Auth oder beschränke zumindest den direkten öffentlichen Traffic. Das Frontend ist yourdomain.com. Zwei separate Deployments. — I use
cms.yourdomain.comorapi.yourdomain.com. Keep it behind basic auth or at minimum restrict direct public traffic. The frontend isyourdomain.com. Two separate deployments. - Installiere das [WPGraphQL Plugin](https://www.wpgraphql.com/) — ich bevorzuge GraphQL gegenüber REST für Content-Seiten, weil du deine Queries mit deinen Komponenten co-locaten kannst und genau die Felder abrufst, die du brauchst. Kein Over-Fetching. Die REST API funktioniert, aber sobald du 15+ Custom Fields pro Post Type hast, ist der GraphQL-Ansatz merklich sauberer. — I prefer GraphQL over REST for content sites because you can co-locate your queries with your components and fetch exactly the fields you need. No over-fetching. The REST API is fine, but once you've got 15+ custom fields per post type, the GraphQL approach is noticeably cleaner.
- Installiere Advanced Custom Fields (ACF) und die WPGraphQL für ACF-Erweiterung. Diese Kombination macht WordPress als Headless-Content-Modell wirklich flexibel — du kannst strukturierte Daten pro Post Type definieren, sie über GraphQL exponieren und Astro verbraucht sie sauber. and the WPGraphQL for ACF extension. This combo is what makes WordPress genuinely flexible as a headless content model — you can define structured data per post type, expose it through GraphQL, and Astro consumes it cleanly.
- Deaktiviere Kommentare, Emojis und das Standard-XML-RPC, falls du das noch nicht getan hast. Diese fügen Overhead und Angriffsfläche hinzu, die du nicht brauchst. if you haven't already. These add overhead and attack surface you don't need.
- Stelle Permalinks auf etwas Sinnvolles ein, bevor du anfängst zu bauen. Sie in der Mitte des Projekts zu ändern, wenn deine Astro-Routes bereits gesetzt sind, ist ein echtes Ärgernis. to something sensible before you start building. Changing them mid-project when your Astro routes are already set is a genuine pain.
Eine Sache, die Leute stolpern lässt: CORS. Standardmäßig lässt WordPress deinen Astro Dev Server (läuft auf localhost:4321) keine Anfragen an deine WP-Installation machen. Gib das in die functions.php deines Themes oder ein kleines Utility Plugin während der Entwicklung:localhost:4321) make requests to your WP install. Drop this into your theme's functions.php or a small utility plugin during development:
`` add_action('init', function() { header("Access-Control-Allow-Origin: *"); }); `` add_action('init', function() { header("Access-Control-Allow-Origin: *"); }); ``
Straffe das in der Produktion auf spezifische Origins ab. Offensichtlich.
---
Die Astro-Projektstruktur
Ich halte das durchdacht und konsistent über Projekte hinweg. Nach einem Dutzend oder so Headless-Builds ist das, was funktioniert:
`` src/ components/ layouts/ pages/ index.astro blog/ [slug].astro lib/ wpgraphql.ts ← all WP query logic lives here styles/ `` src/ components/ layouts/ pages/ index.astro blog/ [slug].astro lib/ wpgraphql.ts ← all WP query logic lives here styles/ ``
The lib/wpgraphql.ts file is where I centralise every GraphQL fetch. No inline fetch calls scattered across page files. Every query is a named, exported async function. Debugging this across 14,000 posts when something breaks at 2am — you'll thank yourself later.lib/wpgraphql.ts file is where I centralise every GraphQL fetch. No inline fetch calls scattered across page files. Every query is a named, exported async function. Debugging this across 14,000 posts when something breaks at 2am — you'll thank yourself later.
Fetching Posts at Build Time
Astro's getStaticPaths is your bread and butter here. For a blog with thousands of posts:getStaticPaths is your bread and butter here. For a blog with thousands of posts:
`` export async function getStaticPaths() { const posts = await getAllPostSlugs(); // calls WPGraphQL return posts.map(post => ({ params: { slug: post.slug }, })); } `` export async function getStaticPaths() { const posts = await getAllPostSlugs(); // calls WPGraphQL return posts.map(post => ({ params: { slug: post.slug }, })); } ``
getAllPostSlugs paginates through WPGraphQL using after cursors — WordPress's GraphQL layer returns 100 posts per request by default, so for 14,000 posts you're making 140 requests at build time. That sounds scary. In practice, on a decent server, the full build runs in about 4–5 minutes. Perfectly acceptable for a site that rebuilds a few times per day. paginates through WPGraphQL using after cursors — WordPress's GraphQL layer returns 100 posts per request by default, so for 14,000 posts you're making 140 requests at build time. That sounds scary. In practice, on a decent server, the full build runs in about 4–5 minutes. Perfectly acceptable for a site that rebuilds a few times per day.
---
Handling Images Without Losing Your Mind
This is the bit nobody talks about enough. WordPress stores image URLs pointing to your CMS subdomain. When Astro builds statically, those images still live on cms.yourdomain.com — which means your visitors' browsers are fetching images from your WordPress server, potentially bypassing your CDN.cms.yourdomain.com — which means your visitors' browsers are fetching images from your WordPress server, potentially bypassing your CDN.
A few ways I handle this:
- Cloudflare vor beiden Domains. Die einfachste Option. Proxye beide yourdomain.com und cms.yourdomain.com durch Cloudflare, konfiguriere aggressive Caching-Regeln auf /wp-content/uploads/*, und es läuft weitgehend problemlos. Simplest option. Proxy both
yourdomain.comandcms.yourdomain.comthrough Cloudflare, configure aggressive caching on/wp-content/uploads/*, and you're mostly fine. - Nutze ein Media-Offload-Plugin. Mir gefällt WP Offload Media — es verlagert Uploads zu S3 (oder kompatiblem Speicher) und schreibt URLs automatisch um. Das ist der Ansatz, den ich für jede Site mit ernsthaftem Traffic nutze. Dein WordPress-Server bedient dann keine Bilder mehr. I like WP Offload Media — it moves uploads to S3 (or compatible storage) and rewrites URLs automatically. This is the approach I use for any site expecting serious traffic. Your WordPress server stops serving images entirely.
- Astros Image-Komponente. Bei Bildern, die du zur Build-Zeit kontrollierst (Featured Images, die via GraphQL abgerufen werden), kannst du die Remote-URL in Astros <Image>-Komponente übergeben und sie optimiert, vergrößert und bedient die Bilder aus deinem Build-Output. Funktioniert hervorragend. Funktioniert nicht für Bilder, die im Post-Body-HTML eingebettet sind — dafür brauchst du einen anderen Durchlauf. For images you control at build time (featured images pulled via GraphQL), you can pass the remote URL into Astro's
<Image>component and it'll optimise, resize, and serve them from your build output. Works brilliantly. Does not work for images embedded in post body HTML — that requires a different pass.
Seahawk hatte letztes Jahr einen Travel-Content-Kunden — etwa 8.000 Posts, extrem bilderlastig, durchschnittlich 12 Bilder pro Artikel. Der WordPress-Server wurde rein durch Bildrequests gehämmert, selbst mit dem Headless-Setup. Der Umstieg auf S3 + CloudFront hat die Origin-Bandbreite um 94% reduziert. Genuiner Game-Changer für ihre Hosting-Kosten.
---
Inkrementelle Builds und das Rebuild-Problem
Hier ist ein echtes Problem mit statischer Generierung im großen Maßstab: dein Editor veröffentlicht um 15 Uhr eine Post-Korrektur und muss 5 Minuten auf einen vollständigen Rebuild warten. Das ist in einer Newsroom nicht akzeptabel.
Ein paar Ansätze, die ich genutzt habe:
Option 1: Netlify oder Vercel mit On-Demand ISR. Astro unterstützt Server-Side Rendering mit Adaptern — du kannst Astro im Hybrid-Modus laufen lassen, wo die meisten Seiten statisch sind, aber spezifische Routes On-Demand gerendert werden. Bei einer News-Site rendere ich die letzten 30 Tage Posts statisch vor (hohes Traffic, braucht Speed) und setze ältere Archive-Seiten auf On-Demand Server-Rendering. Beste aus beiden Welten. Astro supports server-side rendering with adapters — you can run Astro in hybrid mode where most pages are static but specific routes are rendered on-demand. For a news site, I'll often statically pre-render the last 30 days of posts (high traffic, needs speed) and set older archive pages to server-render on demand. Best of both worlds.
Option 2: Webhook-getriggerte Partial Builds. WordPress feuert einen Webhook beim Post-Speichern (einfach mit dem WP Webhooks Plugin). Der Webhook trifft einen Netlify- oder Vercel-Deploy-Hook. Der Build läuft, er ruft nur das Geänderte ab. Es ist nicht wirklich partial — Astro baut immer noch alles wieder auf — aber wenn du deinen Build schnell hältst, sind 4 Minuten machbar. WordPress fires a webhook on post save (easy with the WP Webhooks plugin). That webhook hits a Netlify or Vercel deploy hook. The build runs, it fetches only what's changed. It's not truly partial — Astro still rebuilds everything — but if you keep your build fast, 4 minutes is workable.
Option 3: Nutzen Sie einfach SSR für das Ganze. Deployen Sie Astro mit dem Node-Adapter auf einem VPS (ich nutze dafür Hetzner — billig, schnell, zuverlässig). Jede Seite wird on request gerendert, Sie cachen aggressiv auf Nginx- oder Cloudflare-Ebene, und Sie haben sofortige Post-Updates. Das würde ich für einen echten Publishing-Betrieb mit über 50.000 Posts machen. Deploy Astro with the Node adapter to a VPS (I use Hetzner for this — cheap, fast, reliable). Every page renders on request, you cache aggressively at the Nginx or Cloudflare level, and you have instant post updates. This is what I'd do for a proper publishing operation over 50,000 posts.
Ehrlich gesagt? Die meisten Websites brauchen nicht die Komplexität von Option 1 oder 3. Ein 4-Minuten-Rebuild, ausgelöst durch einen Webhook, ist für 90% der Content-Sites völlig ausreichend.
---
Performance: Was Sie wirklich bekommen
Bei dem Publisher-Projekt aus der Einleitung — hier ist, was nach der Astro-Migration passiert ist:
- LCP ist auf Mobile von 7,2s auf 1,1s gefallen (getestet mit WebPageTest von einem London-Node)1.1s on mobile (tested with WebPageTest from a London node)
- Total Blocking Time ging von ~800ms auf 0ms (null JS standardmäßig, denken Sie dran)0ms (zero JS by default, remember)
- Ihr Google Search Console Core Web Vitals Report ging von 3% „Good" URLs auf 91% „Good" innerhalb von sechs Wochen nach dem Deployment91% "Good" within six weeks of deployment
- Die Hosting-Kosten sanken, weil ihr WordPress-Server keine Seiten mehr servierte, nur noch API-Responses
Das ist nichts Magisches. Es ist nur das, was passiert, wenn Sie PHP-Rendering aus dem kritischen Pfad entfernen und aufhören, ein 400kb-Theme-JavaScript-Bundle an jeden Leser zu verschicken.
---
Die Dinge, die dich stolpern lassen
Ehrlich gesagt — Fehler, die ich in echten Projekten debuggen musste:
- Entwurf-Vorschauen. Das ist in einem Headless-Setup wirklich lästig. WordPress' native Vorschau verlässt sich auf Front-End-Rendering. Du musst einen benutzerdefinierten Preview-Endpoint in Astro bauen, der einen WordPress-Preview-Nonce akzeptiert und den Entwurf über WPGraphQL abruft. Nicht schwierig, aber es dauert einen Tag, um es richtig zu machen. This is genuinely annoying in a headless setup. WordPress's native preview relies on front-end rendering. You need to build a custom preview endpoint in Astro that accepts a WordPress preview nonce and fetches the draft via WPGraphQL. Not hard, but it takes a day to do properly.
- Weiterleitungen. Falls die alte Website Hunderte von Weiterleitungen in .htaccess hatte, befinden sie sich jetzt auf dem WordPress-Server. Du musst sie entweder in Astros Config replizieren oder WordPress zugänglich halten und bestimmte Pfade proxyen. Ich habe beides gemacht. Replizieren in Astro ist langfristig sauberer. If the old site had hundreds of redirects in
.htaccess, those live on the WordPress server now. You need to either replicate them in Astro's config, or keep WordPress accessible and proxy specific paths. I've done both. Replicating in Astro is cleaner long-term. - Suche. WordPress' eingebaute Suche ist in einem Headless-Setup nutzlos. Ich nutze Algolia mit dem WP Search with Algolia Plugin. Indexiere deine Posts in WP, frage Algolia von einer Astro Island Component ab. Funktioniert gut. WordPress's built-in search is useless in a headless setup. I use Algolia with the WP Search with Algolia plugin. Index your posts in WP, query Algolia from an Astro Island component. Works well.
- Menüs und Navigation. WordPress-Menüs sind merkwürdig knifflig, um sie durch WPGraphQL bereitzustellen. Der wpgraphql-acf-Weg ist oft sauberer — modelliere einfach deine Nav als ACF Repeater und fertig. WordPress menus are weirdly fiddly to expose through WPGraphQL. The
wpgraphql-acfroute often ends up being cleaner — just model your nav as an ACF repeater and call it done.
---
FAQ
Brauche ich WPGraphQL oder kann ich einfach die REST API verwenden?
Du kannst absolut die REST API nutzen — sie ist in den WordPress-Kern integriert und erfordert keine zusätzlichen Plugins. Bei einfachen Seiten mit Standard-Post-Typen und minimalen Custom Fields ist das völlig in Ordnung. GraphQL rechtfertigt seinen Platz, wenn du komplexe Content-Modelle mit vielen Custom Fields pro Typ hast. Die Möglichkeit, genau die Felder zu laden, die du brauchst, in einer einzigen Anfrage, ohne dich mit _embed-Parametern und verschachtelten REST-Calls herumschlagen zu müssen, spart bei jeder Query Zeit. Am Ende deine Entscheidung. Ich finde GraphQL jenseits einer bestimmten Komplexitätsschwelle einfach sauberer._embed parameters and nested REST calls, saves time on every query you write. Up to you. I just find GraphQL cleaner past a certain complexity threshold.
Wie handhabe ich WordPress-Authentifizierung für Mitglieder-only Content?
JWT-Authentifizierung ist der Standard-Ansatz. Installiere das Plugin JWT Authentication for WP REST API, gib Tokens bei der Anmeldung aus, übergib sie in deinen GraphQL-Request-Headern. Auf der Astro-Seite würdest du das mit einer SSR-Route (nicht statisch) handhaben, damit der benutzerspezifische Content pro Request serverseitig abgerufen wird. Versuche das nicht statisch zu machen — das führt ins Chaos.JWT Authentication for WP REST API plugin, issue tokens on login, pass them in your GraphQL request headers. On the Astro side, you'd handle this with an SSR route (not static) so the user-specific content is fetched server-side per request. Don't try to do this statically — that way lies madness.
Ist das Overkill für einen kleinen Blog?
Ja, wahrscheinlich. Wenn du weniger als 500 Beiträge und einen Editor hast, lohnt sich der Aufwand einer Headless-Setup nicht. Nutze einfach ein gutes WordPress-Theme, optimiere deine Bilder und machs dir einfach. Diese Architektur zahlt sich aus, wenn du Volumen, redaktionelle Komplexität oder Traffic-Levels hast, wo Performance wirklich einen Unterschied beim Umsatz macht.
Wie sieht das Hosting-Setup in der Produktion aus?
WordPress (nur CMS) auf einem kleinen VPS oder Managed-WordPress-Host — ich nutze Kinsta oder Cloudways. Astro-Frontend auf Vercel, Netlify oder einem Hetzner VPS mit Nginx je nach Projekt. Cloudflare vor allem. Die Gesamtkosten pro Monat für eine mittelgroße Content-Seite liegen normalerweise bei 60–120 €, was oft weniger ist als das, was Kunden für einen All-in-One-WordPress-Host bezahlt haben, der unter der Last zusammengebrochen ist.less than what clients were paying for an all-in-one WordPress host that was struggling under the load.
---
Die ehrliche Zusammenfassung ist diese: Headless WordPress mit Astro ist einer der besseren Dinge, die Content-Seiten in der letzten Zeit passiert sind. Nicht weil es neu ist, sondern weil das Tooling endlich zur Idee aufgeholt hat. WPGraphQL ist stabil, Astros Build-System ist schnell, und die Performance-Gewinne sind real und messbar.
Bekomme die Architektur früh richtig hin — vor allem deine Image-Strategie und deinen Rebuild-Ansatz — und du wirst später viel weniger Zeit mit Feuerbekämpfung verbringen. Das ist wirklich alles.
