WordPress als Headless CMS: Die moderne Art, Websites zu bauen
Erfahre, wie du WordPress als Headless CMS nutzen und mit modernen Frontend-Frameworks wie Next.js oder Nuxt kombinieren kannst.
WordPress als Headless CMS: Die moderne Art, Websites zu bauen
WordPress betreibt über 40% des Internets, aber wusstest du, dass du es auch als modernes Headless CMS nutzen kannst? In diesem Tutorial zeige ich dir, wie du WordPress mit modernen Frontend-Frameworks kombinierst und das Beste aus beiden Welten rausholst.
Was ist ein Headless CMS?
Ein Headless CMS trennt das Backend (Content Management) vom Frontend (Präsentation). WordPress wird dabei nur für die Inhaltsverwaltung genutzt, während die Darstellung über eine separate Anwendung erfolgt.
Vorteile des Headless-Ansatzes
✅ Performance: Statische Seiten sind blitzschnell ✅ Sicherheit: Kein direkter Zugriff auf WordPress ✅ Flexibilität: Jedes Frontend-Framework möglich ✅ Skalierbarkeit: CDN-Distribution einfach möglich ✅ Developer Experience: Moderne Tools und Workflows
WordPress für Headless vorbereiten
1. REST API aktivieren
WordPress hat eine eingebaute REST API. Prüfe die Verfügbarkeit:
# Teste deine WordPress REST API
curl https://deine-domain.ch/wp-json/wp/v2/posts
2. Nützliche Plugins installieren
// Empfohlene Plugins für Headless WordPress
$headless_plugins = [
'acf-to-rest-api' => 'Advanced Custom Fields in API',
'wp-graphql' => 'GraphQL Alternative zu REST',
'jwt-authentication' => 'Sichere API-Authentifizierung',
'headless-mode' => 'Deaktiviert WordPress Frontend'
];
3. Custom Post Types und Fields
// functions.php - Custom Post Type für Projekte
function create_project_post_type() {
register_post_type('project',
array(
'labels' => array(
'name' => __('Projekte'),
'singular_name' => __('Projekt')
),
'public' => true,
'has_archive' => true,
'show_in_rest' => true, // Wichtig für REST API!
'supports' => array('title', 'editor', 'thumbnail', 'custom-fields'),
'menu_icon' => 'dashicons-portfolio'
)
);
}
add_action('init', 'create_project_post_type');
// REST API erweitern
add_action('rest_api_init', function() {
register_rest_field('project', 'featured_image_url', array(
'get_callback' => function($post) {
return get_the_post_thumbnail_url($post['id'], 'full');
}
));
});
Frontend mit Next.js aufbauen
1. Next.js Projekt erstellen
npx create-next-app@latest wordpress-frontend
cd wordpress-frontend
npm install axios swr
2. WordPress API Service
// lib/wordpress.js
const API_URL = process.env.NEXT_PUBLIC_WORDPRESS_API_URL;
export async function getAllPosts() {
const response = await fetch(`${API_URL}/posts?_embed`);
const posts = await response.json();
return posts;
}
export async function getPost(slug) {
const response = await fetch(
`${API_URL}/posts?slug=${slug}&_embed`
);
const posts = await response.json();
return posts[0];
}
export async function getAllCategories() {
const response = await fetch(`${API_URL}/categories`);
const categories = await response.json();
return categories;
}
3. Blog-Übersicht implementieren
// pages/blog/index.js
import { getAllPosts } from '../../lib/wordpress';
import Link from 'next/link';
import Image from 'next/image';
export default function BlogIndex({ posts }) {
return (
<div className="container mx-auto px-4">
<h1 className="text-4xl font-bold mb-8">Blog</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{posts.map((post) => (
<article key={post.id} className="border rounded-lg overflow-hidden">
{post._embedded?.['wp:featuredmedia']?.[0] && (
<Image
src={post._embedded['wp:featuredmedia'][0].source_url}
alt={post.title.rendered}
width={400}
height={250}
className="w-full h-48 object-cover"
/>
)}
<div className="p-6">
<h2 className="text-xl font-semibold mb-2">
<Link href={`/blog/${post.slug}`}>
{post.title.rendered}
</Link>
</h2>
<div
className="text-gray-600 line-clamp-3"
dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }}
/>
</div>
</article>
))}
</div>
</div>
);
}
export async function getStaticProps() {
const posts = await getAllPosts();
return {
props: { posts },
revalidate: 60 // ISR: Seite alle 60 Sekunden neu generieren
};
}
4. Einzelne Blog-Posts
// pages/blog/[slug].js
import { getPost, getAllPosts } from '../../lib/wordpress';
export default function BlogPost({ post }) {
if (!post) return <div>Loading...</div>;
return (
<article className="container mx-auto px-4 max-w-4xl">
<h1 className="text-4xl font-bold mb-4">
{post.title.rendered}
</h1>
<div className="text-gray-600 mb-8">
{new Date(post.date).toLocaleDateString('de-DE')}
</div>
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
/>
</article>
);
}
export async function getStaticPaths() {
const posts = await getAllPosts();
const paths = posts.map((post) => ({
params: { slug: post.slug }
}));
return { paths, fallback: 'blocking' };
}
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
return {
props: { post },
revalidate: 60
};
}
GraphQL mit WPGraphQL
1. GraphQL Setup
# Im WordPress Plugin-Verzeichnis
wp plugin install wp-graphql --activate
2. GraphQL Queries
// lib/graphql.js
const GRAPHQL_URL = process.env.NEXT_PUBLIC_WORDPRESS_GRAPHQL_URL;
export async function fetchAPI(query, { variables } = {}) {
const headers = { 'Content-Type': 'application/json' };
const res = await fetch(GRAPHQL_URL, {
method: 'POST',
headers,
body: JSON.stringify({ query, variables }),
});
const json = await res.json();
if (json.errors) {
console.error(json.errors);
throw new Error('Failed to fetch API');
}
return json.data;
}
export async function getAllPostsWithSlug() {
const data = await fetchAPI(`
{
posts(first: 10000) {
edges {
node {
slug
}
}
}
}
`);
return data?.posts;
}
export async function getPostAndMorePosts(slug) {
const data = await fetchAPI(
`
query PostBySlug($id: ID!) {
post(id: $id, idType: SLUG) {
title
slug
content
date
featuredImage {
node {
sourceUrl
}
}
categories {
edges {
node {
name
}
}
}
}
posts(first: 3, where: { notIn: [$id] }) {
edges {
node {
title
slug
featuredImage {
node {
sourceUrl
}
}
}
}
}
}
`,
{
variables: {
id: slug,
},
}
);
return data;
}
Advanced Custom Fields (ACF) integrieren
1. ACF Felder erstellen
// ACF Feldgruppe für Projekte
if( function_exists('acf_add_local_field_group') ):
acf_add_local_field_group(array(
'key' => 'group_project_details',
'title' => 'Projekt Details',
'fields' => array(
array(
'key' => 'field_client',
'label' => 'Kunde',
'name' => 'client',
'type' => 'text',
),
array(
'key' => 'field_technologies',
'label' => 'Technologien',
'name' => 'technologies',
'type' => 'repeater',
'sub_fields' => array(
array(
'key' => 'field_tech_name',
'label' => 'Technologie',
'name' => 'name',
'type' => 'text',
),
),
),
array(
'key' => 'field_gallery',
'label' => 'Galerie',
'name' => 'gallery',
'type' => 'gallery',
),
),
'location' => array(
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'project',
),
),
),
'show_in_rest' => true, // Wichtig für REST API
));
endif;
2. ACF Daten im Frontend nutzen
// components/ProjectDetail.js
export default function ProjectDetail({ project }) {
return (
<div className="project-detail">
<h1>{project.title.rendered}</h1>
{project.acf?.client && (
<p className="client">Kunde: {project.acf.client}</p>
)}
{project.acf?.technologies && (
<div className="technologies">
<h3>Verwendete Technologien:</h3>
<ul>
{project.acf.technologies.map((tech, index) => (
<li key={index}>{tech.name}</li>
))}
</ul>
</div>
)}
{project.acf?.gallery && (
<div className="gallery grid grid-cols-3 gap-4">
{project.acf.gallery.map((image) => (
<Image
key={image.id}
src={image.url}
alt={image.alt}
width={400}
height={300}
className="rounded-lg"
/>
))}
</div>
)}
</div>
);
}
Preview-Funktionalität implementieren
1. Preview API Route
// pages/api/preview.js
export default async function preview(req, res) {
const { secret, slug } = req.query;
// Check the secret and next parameters
if (secret !== process.env.WORDPRESS_PREVIEW_SECRET || !slug) {
return res.status(401).json({ message: 'Invalid token' });
}
// Fetch the headless CMS to check if the provided slug exists
const post = await getPostBySlug(slug);
// If the slug doesn't exist prevent preview mode from being enabled
if (!post) {
return res.status(401).json({ message: 'Invalid slug' });
}
// Enable Preview Mode
res.setPreviewData({
post: {
slug: post.slug,
status: post.status,
},
});
// Redirect to the path from the fetched post
res.redirect(`/blog/${post.slug}`);
}
Deployment und Performance
1. Umgebungsvariablen
# .env.local
NEXT_PUBLIC_WORDPRESS_API_URL=https://api.ihre-domain.ch/wp-json/wp/v2
NEXT_PUBLIC_WORDPRESS_GRAPHQL_URL=https://api.ihre-domain.ch/graphql
WORDPRESS_PREVIEW_SECRET=ihr-geheimer-preview-key
2. Build-Optimierungen
// next.config.js
module.exports = {
images: {
domains: ['api.ihre-domain.ch'],
},
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
}
],
},
];
},
};
3. Caching-Strategien
// lib/cache.js
import { unstable_cache } from 'next/cache';
export const getCachedPosts = unstable_cache(
async () => {
return await getAllPosts();
},
['posts'],
{
revalidate: 3600, // 1 Stunde
tags: ['posts'],
}
);
Security Best Practices
1. API-Zugriff absichern
// WordPress: API nur für bestimmte Endpoints öffnen
add_filter('rest_authentication_errors', function($result) {
if (!empty($result)) {
return $result;
}
if (!is_user_logged_in() &&
strpos($_SERVER['REQUEST_URI'], '/wp/v2/posts') === false &&
strpos($_SERVER['REQUEST_URI'], '/wp/v2/pages') === false) {
return new WP_Error('rest_not_logged_in',
'You are not currently logged in.',
array('status' => 401));
}
return $result;
});
2. CORS konfigurieren
// WordPress: CORS Headers
add_action('rest_api_init', function() {
remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
add_filter('rest_pre_serve_request', function($value) {
$origin = get_http_origin();
$allowed_origins = ['https://ihre-frontend-domain.ch'];
if ($origin && in_array($origin, $allowed_origins)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Credentials: true');
}
return $value;
});
});
Fazit
WordPress als Headless CMS bietet das Beste aus zwei Welten: Die vertraute Inhaltsverwaltung von WordPress kombiniert mit der Performance und Flexibilität moderner Frontend-Frameworks.
Mit dieser Architektur kannst du:
- Blitzschnelle Websites bauen
- Moderne Developer-Tools nutzen
- Skalierbare Anwendungen erstellen
- Die Sicherheit erhöhen
Der Umstieg erfordert zwar initial mehr Setup, zahlt sich aber durch bessere Performance, Developer Experience und Zukunftssicherheit aus. Probier es aus - deine User (und Entwickler) werden es dir danken!