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.
dist/
-Ordner (/htdocs/frontend/dist/
).backend.deine-domain.de
) zeigt auf den WordPress-Ordner (/htdocs/backend/
).dist/
zeigen kann, wird eine .htaccess-Weiterleitung verwendet.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)
/backend/
installierenLade WordPress in /htdocs/backend/
hoch und installiere es.
Im Backend:
Einstellungen
→ Allgemein
→ WordPress-URL & Website-URL auf https://backend.deine-domain.de
setzen.Einstellungen
→ Permalinks
→ „Beitragsname“ aktivieren.WordPress braucht ein aktives Theme, selbst als Headless CMS.
/backend/wp-content/themes/headless-theme/
eine style.css
-Datei an:/*
Theme Name: Headless WP
Description: Ein minimalistisches Theme für Headless WordPress
Version: 1.0
*/
/backend/wp-content/themes/headless-theme/
eine index.php
an:<?php
// Headless WordPress Theme - Gibt nichts aus
?>
/backend/wp-content/themes/headless-theme/
eine function.php
-Datei an<?php
// Headless Theme: Verhindert das Laden von HTML-Templates
add_action('template_redirect', function () {
if (!is_admin()) {
wp_redirect(home_url('/wp-admin/'));
exit;
}
});
Design
→ Themes
.Plugins
→ Neu hinzufügen
https://backend.deine-domain.de/graphql
Einstellungen
→ Permalinks
.npm create astro@latest
cd mein-astro-projekt
npm install
npm install graphql
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 || "",
}));
}
.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
index.astro
& [slug].astro
anlegenindex.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>
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.
Falls du möchtest, dass Änderungen in WordPress sofort in Astro sichtbar sind, gibt es zwei Lösungen:
Nutze einen Webhook oder Cron-Job, um npm run build
automatisch auszuführen, wenn ein Beitrag in WordPress aktualisiert wird.
build
-Befehl auf dem Server aus.cd /path/to/astro-project
npm run build
rsync -av ./dist/ /htdocs/frontend/dist/
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