Anleitung: WordPress Headless mit Astro & WPGraphQL

Diese Anleitung beschreibt, wie du WordPress als Headless CMS mit Astro als Frontend nutzt.
Domain-unabhängig formuliert, damit sie auf jedes Setup anwendbar ist.

Server-Setup & Domain/Subdomain richtig einrichten

Ordnerstruktur auf dem Server

Struktur für das Setup:

/htdocs/
├── backend/ ✅ (WordPress Backend)
│ ├── wp-content/
│ ├── wp-admin/
│ ├── wp-includes/
│ ├── wp-config.php
│ ├── .htaccess ✅ (Backend-Routing)
│ ├── …
├── frontend/ ✅ (Astro-Projekt)
│ ├── dist/ ✅ (Astro-Build-Ordner)
│ │ ├── index.html
│ │ ├── [slug]/index.html
│ │ ├── .htaccess ✅ (Astro-Routing)
│ ├── src/
│ ├── public/
│ ├── package.json
│ ├── astro.config.mjs
│ ├── …
└── .htaccess ✅ (Haupt-Routing)

3. WordPress unter /backend/ installieren

Lade WordPress in /htdocs/backend/ hoch und installiere es.

Im Backend:

Headless-Theme installieren

WordPress braucht ein aktives Theme, selbst als Headless CMS.

Headless-Theme erstellen:

/*
Theme Name: Headless WP
Description: Ein minimalistisches Theme für Headless WordPress
Version: 1.0
*/
<?php
// Headless WordPress Theme - Gibt nichts aus
?>
<?php
// Headless Theme: Verhindert das Laden von HTML-Templates
add_action('template_redirect', function () {
    if (!is_admin()) {
        wp_redirect(home_url('/wp-admin/'));
        exit;
    }
});

Aktiviere es in WordPress unter DesignThemes.

WPGraphQL als Plugin installieren

Im WordPress-Admin:

Testen, ob die GraphQL-API funktioniert:

Permalinks als „Beitragsname“ aktivieren

Astro einrichten & mit WPGraphQL verbinden

Astro-Projekt erstellen (falls nicht vorhanden):

npm create astro@latest
cd mein-astro-projekt
npm install

GraphQL-Client in Astro installieren:

npm install graphql

API-Datei in Astro anlegen:

Erstelle src/api/wpGraphQL.js


export async function getPosts() {
    const response = await fetch("https://backend.keindritterweltkrieg.de/graphql", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        query: `
          {
            posts(first: 5) {
              nodes {
                id
                title
                slug
                excerpt
                content
                date
              }
            }
          }
        `,
      }),
    });
  
    const json = await response.json();
    console.log("DEBUG: API Antwort:", JSON.stringify(json, null, 2)); 
  
    if (!json.data || !json.data.posts || !json.data.posts.nodes) {
      throw new Error("Keine gültigen Blogposts aus WordPress erhalten!");
    }
  
    return json.data.posts.nodes.map(post => ({
      ...post,
      content: post.content?.rendered || post.content || "", 
    }));
  }
  

8. .htaccess-Dateien für Routing erstellen

.htaccess in /htdocs/frontend/dist/ (Astro-Ordner)

Ermöglicht Astro’s statisches Routing

RewriteEngine On
RewriteBase /

# Falls die Datei existiert, lade sie direkt (z. B. CSS, Bilder, JS)
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Weiterleitung aller nicht existierenden Dateien an Astro (index.html)
RewriteRule ^.*$ /index.html [L]

.htaccess in /htdocs/backend/ (WordPress)

Standard-WordPress-Routing

# BEGIN WordPress
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress

9. Astro-Frontend: index.astro & [slug].astro anlegen

index.astro (Startseite mit Blogposts)

Erstelle src/pages/index.astro

---
import { getPosts } from "../api/wpGraphQL.js";
const posts = await getPosts();
---

<html lang="de">
  <head>
    <meta charset="UTF-8">
    <title>WPGraphQL + Astro</title>
  </head>
  <body>
    <h1>Neueste Blogposts</h1>
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          <a href={`/${post.slug}`}>
            <h2>{post.title}</h2>
          </a>
          <p set:html={post.excerpt}></p>
          <small>Veröffentlicht am: {new Date(post.date).toLocaleDateString("de-DE")}</small>
        </li>
      ))}
    </ul>
  </body>
</html>

[slug].astro (Blogpost-Seiten)

Erstelle src/pages/[slug].astro

---
import { getPosts } from "../api/wpGraphQL.js";

export async function getStaticPaths() {
  try {
    const posts = await getPosts();
    return posts.map(post => ({
      params: { slug: post.slug },
      props: { post },
    }));
  } catch (error) {
    console.error("🚨 Fehler beim Abrufen der Blogposts:", error);
    return [];
  }
}

const { slug } = Astro.params;
const posts = await getPosts();
const post = posts.find(p => p.slug === slug);

if (!post) {
  throw new Error(`🚨 Post nicht gefunden: ${slug}`);
}

console.log("📌 DEBUG: Post Inhalt nach Fix:", post.content); // 🔥 Prüfen, ob HTML-String ankommt
---

<html lang="de">
  <head>
    <meta charset="UTF-8">
    <title>{post?.title}</title>
  </head>
  <body>
    <h1>{post?.title}</h1>
    <div set:html={post?.content}></div> <!-- 🔥 Fix: Richtiges HTML-Rendering -->
    <p>Veröffentlicht am: {new Date(post?.date).toLocaleDateString("de-DE")}</p>
    <a href="/">← Zurück</a>
  </body>
</html>


Was passiert, wenn du den Beitrag „Hallo Welt“ in WordPress änderst?

Da Astro statisch generiert wird, bleibt der Inhalt von /hallo-welt/ so, wie er beim letzten npm run build war. Das bedeutet:

Der Beitrag wird in WordPress aktualisiert.
Die Änderungen sind aber nicht sofort im Astro-Frontend sichtbar.

Wie kannst du Änderungen automatisch übernehmen?

Falls du möchtest, dass Änderungen in WordPress sofort in Astro sichtbar sind, gibt es zwei Lösungen:

Lösung 1: Automatischer Rebuild bei Änderungen (Empfohlen für kleine Seiten)

Nutze einen Webhook oder Cron-Job, um npm run build automatisch auszuführen, wenn ein Beitrag in WordPress aktualisiert wird.

cd /path/to/astro-project
npm run build
rsync -av ./dist/ /htdocs/frontend/dist/

Lösung 2: Astro als „Hybrid“ mit dynamischen API-Aufrufen (Empfohlen für häufige Änderungen)

Falls du kein statisches Rebuild nach jeder Änderung willst, kannst du die Beiträge beim Seitenaufruf aus WordPress abrufen, statt sie statisch zu generieren.

Ersetze getStaticPaths() durch eine Live-Abfrage in [slug].astro 📌 Öffne src/pages/[slug].astro

---
import { Astro } from "astro";
import { getPosts } from "../api/wpGraphQL.js";

// Hole Post beim Seitenaufruf (Dynamisch)
const { slug } = Astro.params;
const posts = await getPosts();
const post = posts.find(p => p.slug === slug);
---

<html lang="de">
  <head>
    <title>{post?.title}</title>
  </head>
  <body>
    <h1>{post?.title}</h1>
    <div set:html={post.excerpt}></div>
    <p>Veröffentlicht am: {new Date(post?.date).toLocaleDateString("de-DE")}</p>
    <a href="/">← Zurück</a>
  </body>
</html>

Veröffentlicht am: 23.2.2025

← Zurück