WordPress als Headless CMS mit Next.js - Vollstaendiger Leitfaden
WordPress als Headless
backendWordPress als Headless CMS mit Next.js
WordPress betreibt ueber 40% aller Websites im Internet. Die traditionelle monolithische Architektur, bei der WordPress sowohl fuer die Inhaltsverwaltung als auch fuer die Darstellung zustaendig ist, weicht jedoch zunehmend dem modernen Headless-Ansatz. In diesem Artikel zeigen wir, wie Sie die Leistungsfaehigkeit von WordPress als Content-Management-System mit der Performance und Flexibilitaet von Next.js im Frontend kombinieren koennen.
Was ist ein Headless CMS?#
Ein Headless CMS ist ein Content-Management-System, das die Backend-Schicht (Inhaltserstellung und -speicherung) von der Frontend-Schicht (Inhaltsdarstellung) trennt. Anstatt HTML-Seiten direkt zu generieren, stellt ein Headless CMS Inhalte ueber eine API bereit -- typischerweise REST oder GraphQL.
Warum WordPress als Headless CMS verwenden?#
- Vertraute Oberflaeche -- Redakteure kennen bereits das WordPress-Admin-Panel
- Reichhaltiges Plugin-Oekosystem -- Tausende von Plugins zur Funktionserweiterung
- Plattformreife -- ueber 20 Jahre Entwicklung und Stabilitaet
- Grosse Community -- einfacher Zugang zu Support und Dokumentation
- Volle Frontend-Kontrolle -- beliebige Technologie fuer die Inhaltsdarstellung
- Bessere Performance -- von Next.js generierte statische Seiten sind blitzschnell
- Sicherheit -- das Frontend ist vom Admin-Panel getrennt
Headless vs Traditionelles WordPress#
| Merkmal | Traditionelles WordPress | Headless WordPress | |---------|--------------------------|-------------------| | Rendering | Serverseitiges PHP | SSG/SSR/ISR in Next.js | | Performance | Abhaengig von Server und Plugins | Statische Seiten, CDN | | Frontend-Flexibilitaet | Auf PHP-Templates beschraenkt | Volle Freiheit (React, Vue, etc.) | | SEO | Erfordert Plugins (Yoast) | In Next.js integriert | | Sicherheit | Admin-Panel exponiert | Backend verborgen, statisches Frontend | | Skalierbarkeit | Erfordert Caching und Optimierung | Natuerlich skalierbar ueber CDN |
WordPress REST API#
Seit Version 4.7 verfuegt WordPress ueber eine integrierte REST API, die alle grundlegenden Inhaltstypen bereitstellt. Die API ist unter /wp-json/wp/v2/ verfuegbar.
Grundlegende Endpunkte#
# Beitraege abrufen
GET /wp-json/wp/v2/posts
# Einzelnen Beitrag abrufen
GET /wp-json/wp/v2/posts/123
# Seiten abrufen
GET /wp-json/wp/v2/pages
# Kategorien abrufen
GET /wp-json/wp/v2/categories
# Medien abrufen
GET /wp-json/wp/v2/media
# Benutzer abrufen
GET /wp-json/wp/v2/users
Filterung und Paginierung#
Die REST API bietet umfangreiche Filtermoeglichkeiten:
# Beitraege aus einer bestimmten Kategorie
GET /wp-json/wp/v2/posts?categories=5
# Suche
GET /wp-json/wp/v2/posts?search=nextjs
# Paginierung (10 Beitraege pro Seite)
GET /wp-json/wp/v2/posts?per_page=10&page=2
# Sortierung
GET /wp-json/wp/v2/posts?orderby=date&order=desc
# Einbettung verwandter Daten (Autor, Medien, Kategorien)
GET /wp-json/wp/v2/posts?_embed
Registrierung eigener Endpunkte#
Sie koennen die REST API um eigene Endpunkte erweitern:
// functions.php
add_action('rest_api_init', function () {
register_rest_route('custom/v1', '/featured-posts', [
'methods' => 'GET',
'callback' => 'get_featured_posts',
'permission_callback' => '__return_true',
]);
});
function get_featured_posts($request) {
$posts = get_posts([
'meta_key' => 'is_featured',
'meta_value' => '1',
'numberposts' => 6,
]);
$data = [];
foreach ($posts as $post) {
$data[] = [
'id' => $post->ID,
'title' => $post->post_title,
'excerpt' => get_the_excerpt($post),
'slug' => $post->post_name,
'thumbnail' => get_the_post_thumbnail_url($post, 'large'),
];
}
return rest_ensure_response($data);
}
WPGraphQL -- GraphQL fuer WordPress#
Obwohl die REST API funktional ist, bietet GraphQL eine deutlich flexiblere Datenabfrage. Das Plugin WPGraphQL fuegt WordPress volle GraphQL-Unterstuetzung hinzu.
WPGraphQL installieren#
# Ueber WP-CLI
wp plugin install wp-graphql --activate
# Oder herunterladen von https://www.wpgraphql.com/
Nach der Installation ist der GraphQL-Endpunkt unter /graphql verfuegbar.
Beispiel-GraphQL-Abfragen#
# Beitraege mit Autor und Kategorien abrufen
query GetPosts {
posts(first: 10, where: { orderby: { field: DATE, order: DESC } }) {
nodes {
id
databaseId
title
slug
excerpt
date
featuredImage {
node {
sourceUrl
altText
mediaDetails {
width
height
}
}
}
author {
node {
name
avatar {
url
}
}
}
categories {
nodes {
name
slug
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
# Einzelnen Beitrag nach Slug abrufen
query GetPostBySlug($slug: ID!) {
post(id: $slug, idType: SLUG) {
title
content
date
modified
seo {
title
metaDesc
opengraphImage {
sourceUrl
}
}
}
}
Vorteile von GraphQL gegenueber REST API#
- Genau die benoetigten Daten abrufen -- kein Over-Fetching
- Eine Abfrage statt vieler -- weniger HTTP-Anfragen
- Starke Typisierung -- bessere Validierung und IDE-Autovervollstaendigung
- Introspektion -- automatische Schema-Dokumentation
Next.js mit WordPress einrichten#
Projektinitialisierung#
npx create-next-app@latest wordpress-frontend --typescript --app
cd wordpress-frontend
npm install graphql-request graphql
Umgebungsvariablen konfigurieren#
# .env.local
NEXT_PUBLIC_WORDPRESS_URL=https://cms.ihredomain.de
WORDPRESS_GRAPHQL_URL=https://cms.ihredomain.de/graphql
WORDPRESS_AUTH_REFRESH_TOKEN=ihr-refresh-token
WORDPRESS_PREVIEW_SECRET=ihr-vorschau-geheimnis
REVALIDATION_SECRET=ihr-revalidierungs-geheimnis
GraphQL-Client#
// lib/wordpress.ts
import { GraphQLClient, gql } from 'graphql-request';
const client = new GraphQLClient(
process.env.WORDPRESS_GRAPHQL_URL!,
{
headers: {
'Content-Type': 'application/json',
},
}
);
// Typen
export interface WPPost {
id: string;
databaseId: number;
title: string;
slug: string;
excerpt: string;
content: string;
date: string;
modified: string;
featuredImage: {
node: {
sourceUrl: string;
altText: string;
mediaDetails: {
width: number;
height: number;
};
};
} | null;
author: {
node: {
name: string;
avatar: { url: string };
};
};
categories: {
nodes: Array<{ name: string; slug: string }>;
};
seo?: {
title: string;
metaDesc: string;
opengraphImage?: { sourceUrl: string };
};
}
// Alle Beitraege abrufen
export async function getAllPosts(first = 20): Promise<WPPost[]> {
const query = gql`
query GetAllPosts($first: Int!) {
posts(first: $first, where: { orderby: { field: DATE, order: DESC } }) {
nodes {
id
databaseId
title
slug
excerpt
date
featuredImage {
node {
sourceUrl
altText
mediaDetails {
width
height
}
}
}
author {
node {
name
avatar {
url
}
}
}
categories {
nodes {
name
slug
}
}
}
}
}
`;
const data = await client.request<{ posts: { nodes: WPPost[] } }>(
query,
{ first }
);
return data.posts.nodes;
}
// Beitrag nach Slug abrufen
export async function getPostBySlug(slug: string): Promise<WPPost | null> {
const query = gql`
query GetPostBySlug($slug: ID!) {
post(id: $slug, idType: SLUG) {
id
databaseId
title
slug
content
excerpt
date
modified
featuredImage {
node {
sourceUrl
altText
mediaDetails {
width
height
}
}
}
author {
node {
name
avatar {
url
}
}
}
categories {
nodes {
name
slug
}
}
seo {
title
metaDesc
opengraphImage {
sourceUrl
}
}
}
}
`;
const data = await client.request<{ post: WPPost | null }>(
query,
{ slug }
);
return data.post;
}
// Alle Beitrags-Slugs abrufen (fuer generateStaticParams)
export async function getAllPostSlugs(): Promise<string[]> {
const query = gql`
query GetAllSlugs {
posts(first: 1000) {
nodes {
slug
}
}
}
`;
const data = await client.request<{
posts: { nodes: Array<{ slug: string }> };
}>(query);
return data.posts.nodes.map((node) => node.slug);
}
Blog-Listenseite#
// app/blog/page.tsx
import { getAllPosts } from '@/lib/wordpress';
import Image from 'next/image';
import Link from 'next/link';
export const revalidate = 3600; // ISR: Revalidierung jede Stunde
export default async function BlogPage() {
const posts = await getAllPosts();
return (
<main className="container mx-auto px-4 py-12">
<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-8">
{posts.map((post) => (
<article key={post.id} className="bg-white rounded-lg shadow-md overflow-hidden">
{post.featuredImage && (
<Image
src={post.featuredImage.node.sourceUrl}
alt={post.featuredImage.node.altText || post.title}
width={post.featuredImage.node.mediaDetails.width}
height={post.featuredImage.node.mediaDetails.height}
className="w-full h-48 object-cover"
/>
)}
<div className="p-6">
<div className="flex gap-2 mb-3">
{post.categories.nodes.map((cat) => (
<span key={cat.slug} className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
{cat.name}
</span>
))}
</div>
<h2 className="text-xl font-semibold mb-2">
<Link href={`/blog/${post.slug}`} className="hover:text-blue-600">
{post.title}
</Link>
</h2>
<div
className="text-gray-600 text-sm mb-4"
dangerouslySetInnerHTML={{ __html: post.excerpt }}
/>
<div className="flex items-center text-sm text-gray-500">
<span>{post.author.node.name}</span>
<span className="mx-2">|</span>
<time dateTime={post.date}>
{new Date(post.date).toLocaleDateString('de-DE')}
</time>
</div>
</div>
</article>
))}
</div>
</main>
);
}
Einzelne Beitragsseite#
// app/blog/[slug]/page.tsx
import { getPostBySlug, getAllPostSlugs } from '@/lib/wordpress';
import Image from 'next/image';
import { notFound } from 'next/navigation';
import { Metadata } from 'next';
export const revalidate = 3600;
interface PageProps {
params: Promise<{ slug: string }>;
}
export async function generateStaticParams() {
const slugs = await getAllPostSlugs();
return slugs.map((slug) => ({ slug }));
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) return {};
return {
title: post.seo?.title || post.title,
description: post.seo?.metaDesc || post.excerpt.replace(/<[^>]*>/g, ''),
openGraph: {
title: post.seo?.title || post.title,
description: post.seo?.metaDesc || post.excerpt.replace(/<[^>]*>/g, ''),
images: post.seo?.opengraphImage
? [{ url: post.seo.opengraphImage.sourceUrl }]
: post.featuredImage
? [{ url: post.featuredImage.node.sourceUrl }]
: [],
},
};
}
export default async function PostPage({ params }: PageProps) {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) notFound();
return (
<article className="container mx-auto px-4 py-12 max-w-3xl">
<header className="mb-8">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<div className="flex items-center text-gray-600 mb-6">
<span>{post.author.node.name}</span>
<span className="mx-2">|</span>
<time dateTime={post.date}>
{new Date(post.date).toLocaleDateString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
</div>
{post.featuredImage && (
<Image
src={post.featuredImage.node.sourceUrl}
alt={post.featuredImage.node.altText || post.title}
width={post.featuredImage.node.mediaDetails.width}
height={post.featuredImage.node.mediaDetails.height}
className="w-full rounded-lg"
priority
/>
)}
</header>
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
</article>
);
}
Custom Post Types und Advanced Custom Fields (ACF)#
Einer der staerksten Aspekte von WordPress sind benutzerdefinierte Inhaltstypen in Kombination mit ACF-Feldern. Um diese ueber GraphQL bereitzustellen, benoetigen Sie das zusaetzliche Plugin WPGraphQL for ACF.
Registrierung eines Custom Post Type#
// functions.php
add_action('init', function () {
register_post_type('portfolio', [
'labels' => [
'name' => 'Portfolio',
'singular_name' => 'Projekt',
],
'public' => true,
'has_archive' => true,
'show_in_rest' => true, // Erforderlich fuer REST API
'show_in_graphql' => true, // Erforderlich fuer WPGraphQL
'graphql_single_name' => 'project',
'graphql_plural_name' => 'projects',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
]);
});
ACF-Konfiguration mit GraphQL#
Nach der Installation von WPGraphQL for ACF sind ACF-Felder automatisch im GraphQL-Schema verfuegbar:
query GetProjects {
projects(first: 12) {
nodes {
title
slug
excerpt
featuredImage {
node {
sourceUrl
altText
}
}
projectFields {
clientName
projectUrl
technologies
completionDate
testimonial
}
}
}
}
ACF-Daten in Next.js abrufen#
// lib/wordpress.ts
export interface Project {
title: string;
slug: string;
excerpt: string;
featuredImage: {
node: {
sourceUrl: string;
altText: string;
};
} | null;
projectFields: {
clientName: string;
projectUrl: string;
technologies: string[];
completionDate: string;
testimonial: string;
};
}
export async function getProjects(): Promise<Project[]> {
const query = gql`
query GetProjects {
projects(first: 50) {
nodes {
title
slug
excerpt
featuredImage {
node {
sourceUrl
altText
}
}
projectFields {
clientName
projectUrl
technologies
completionDate
testimonial
}
}
}
}
`;
const data = await client.request<{
projects: { nodes: Project[] };
}>(query);
return data.projects.nodes;
}
Bildoptimierung mit Next.js Image#
WordPress speichert Bilder auf dem eigenen Server, aber Next.js kann sie mithilfe der Image-Komponente im laufenden Betrieb optimieren.
next.config.js Konfiguration#
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cms.ihredomain.de',
pathname: '/wp-content/uploads/**',
},
],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
module.exports = nextConfig;
WordPress-Bildkomponente#
// components/WordPressImage.tsx
import Image from 'next/image';
interface WordPressImageProps {
src: string;
alt: string;
width: number;
height: number;
priority?: boolean;
className?: string;
sizes?: string;
}
export function WordPressImage({
src,
alt,
width,
height,
priority = false,
className,
sizes = '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw',
}: WordPressImageProps) {
return (
<Image
src={src}
alt={alt}
width={width}
height={height}
priority={priority}
className={className}
sizes={sizes}
quality={85}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."
/>
);
}
Incremental Static Regeneration (ISR)#
ISR ist eine Schluesselfunktion von Next.js, die es ermoeglicht, statische Seiten ohne vollstaendigen Rebuild zu aktualisieren. Es ist die ideale Loesung fuer WordPress-Inhalte, die sich regelmaessig aendern.
Zeitbasierte Revalidierung#
// app/blog/page.tsx
export const revalidate = 3600; // Revalidierung jede Stunde
On-Demand-Revalidierung#
Ein besserer Ansatz ist die durch einen Webhook von WordPress ausgeloeste Revalidierung:
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.headers.get('x-revalidation-secret');
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ message: 'Invalid secret' }, { status: 401 });
}
const body = await request.json();
const { post_type, slug } = body;
try {
// Bestimmten Beitrag revalidieren
if (slug) {
revalidatePath(`/blog/${slug}`);
}
// Blog-Auflistung revalidieren
revalidatePath('/blog');
// Nach Tag revalidieren
revalidateTag('wordpress-posts');
return NextResponse.json({
revalidated: true,
date: new Date().toISOString(),
});
} catch (error) {
return NextResponse.json(
{ message: 'Error revalidating' },
{ status: 500 }
);
}
}
WordPress-Webhook#
// functions.php
add_action('save_post', function ($post_id, $post) {
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
if ($post->post_status !== 'publish') {
return;
}
$webhook_url = 'https://ihredomain.de/api/revalidate';
wp_remote_post($webhook_url, [
'headers' => [
'Content-Type' => 'application/json',
'x-revalidation-secret' => defined('REVALIDATION_SECRET')
? REVALIDATION_SECRET
: '',
],
'body' => json_encode([
'post_type' => $post->post_type,
'slug' => $post->post_name,
'post_id' => $post_id,
]),
'timeout' => 10,
]);
}, 10, 2);
Authentifizierung und Vorschaumodus#
Der Vorschaumodus ermoeglicht es Redakteuren, unveroeffentlichte Inhaltsaenderungen direkt im Next.js-Frontend zu sehen.
Vorschau-Endpunkt in Next.js#
// app/api/preview/route.ts
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret');
const slug = request.nextUrl.searchParams.get('slug');
const postType = request.nextUrl.searchParams.get('post_type') || 'post';
if (secret !== process.env.WORDPRESS_PREVIEW_SECRET || !slug) {
return new Response('Invalid token', { status: 401 });
}
const draft = await draftMode();
draft.enable();
const path = postType === 'page' ? `/${slug}` : `/blog/${slug}`;
redirect(path);
}
// app/api/exit-preview/route.ts
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';
export async function GET() {
const draft = await draftMode();
draft.disable();
redirect('/');
}
Entwuerfe aus WordPress abrufen#
// lib/wordpress.ts
export async function getPreviewPost(
id: number,
authToken: string
): Promise<WPPost | null> {
const authenticatedClient = new GraphQLClient(
process.env.WORDPRESS_GRAPHQL_URL!,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`,
},
}
);
const query = gql`
query GetPreviewPost($id: ID!) {
post(id: $id, idType: DATABASE_ID, asPreview: true) {
title
content
slug
date
featuredImage {
node {
sourceUrl
altText
mediaDetails {
width
height
}
}
}
}
}
`;
const data = await authenticatedClient.request<{ post: WPPost | null }>(
query,
{ id }
);
return data.post;
}
Vorschau in WordPress konfigurieren#
// functions.php
add_filter('preview_post_link', function ($link, $post) {
$frontend_url = 'https://ihredomain.de';
$secret = defined('PREVIEW_SECRET') ? PREVIEW_SECRET : '';
$slug = $post->post_name ?: $post->ID;
return sprintf(
'%s/api/preview?secret=%s&slug=%s&post_type=%s',
$frontend_url,
$secret,
$slug,
$post->post_type
);
}, 10, 2);
SEO im Headless WordPress#
SEO ist einer der Hauptgruende, warum Unternehmen WordPress waehlen. In einer Headless-Architektur gehen diese Faehigkeiten nicht verloren -- sie werden sogar verbessert.
Yoast SEO Integration#
Das Plugin WPGraphQL Yoast SEO stellt Yoast-SEO-Daten ueber GraphQL bereit:
query GetPostSEO($slug: ID!) {
post(id: $slug, idType: SLUG) {
seo {
title
metaDesc
canonical
opengraphTitle
opengraphDescription
opengraphImage {
sourceUrl
mediaDetails {
width
height
}
}
twitterTitle
twitterDescription
twitterImage {
sourceUrl
}
schema {
raw
}
}
}
}
Metadaten in Next.js generieren#
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post?.seo) return {};
return {
title: post.seo.title,
description: post.seo.metaDesc,
alternates: {
canonical: post.seo.canonical || `/blog/${slug}`,
},
openGraph: {
title: post.seo.opengraphTitle || post.seo.title,
description: post.seo.opengraphDescription || post.seo.metaDesc,
type: 'article',
publishedTime: post.date,
modifiedTime: post.modified,
images: post.seo.opengraphImage
? [{
url: post.seo.opengraphImage.sourceUrl,
width: post.seo.opengraphImage.mediaDetails?.width,
height: post.seo.opengraphImage.mediaDetails?.height,
}]
: [],
},
twitter: {
card: 'summary_large_image',
title: post.seo.twitterTitle || post.seo.title,
description: post.seo.twitterDescription || post.seo.metaDesc,
},
};
}
Sitemap generieren#
// app/sitemap.ts
import { getAllPosts } from '@/lib/wordpress';
export default async function sitemap() {
const posts = await getAllPosts(1000);
const blogEntries = posts.map((post) => ({
url: `https://ihredomain.de/blog/${post.slug}`,
lastModified: new Date(post.modified || post.date),
changeFrequency: 'weekly' as const,
priority: 0.7,
}));
return [
{
url: 'https://ihredomain.de',
lastModified: new Date(),
changeFrequency: 'daily' as const,
priority: 1,
},
{
url: 'https://ihredomain.de/blog',
lastModified: new Date(),
changeFrequency: 'daily' as const,
priority: 0.9,
},
...blogEntries,
];
}
Leistungsvergleich: Headless vs Traditionell#
Tests an einem realen Projekt mit 500 Beitraegen zeigen signifikante Unterschiede:
| Metrik | Traditionelles WordPress | Headless (Next.js + WP) | |--------|--------------------------|------------------------| | TTFB | 800-1500 ms | 50-150 ms | | LCP | 2,5-4,0 s | 0,8-1,5 s | | FID / INP | 150-300 ms | 30-80 ms | | CLS | 0,1-0,25 | 0,01-0,05 | | Lighthouse Score | 55-75 | 90-100 | | Build-Zeit (500 Beitraege) | N/A | ~3-5 Min. | | Hosting-Kosten | 20-50 EUR/Monat (gutes Hosting) | 0-20 EUR/Monat (Vercel free/pro) |
Hauptfaktoren fuer die Leistungsverbesserung:
- Statische Seiten -- HTML vorab generiert, ueber CDN bereitgestellt
- Bildoptimierung -- automatische Konvertierung in WebP/AVIF, Lazy Loading
- Code Splitting -- nur notwendiges JavaScript pro Seite geladen
- Edge Caching -- Inhalte vom naechstgelegenen CDN-Knoten bereitgestellt
Deployment-Strategien#
Zielarchitektur#
[Redakteur] --> [WordPress CMS] --> [Webhook]
| |
v v
[WPGraphQL API] [On-Demand ISR]
| |
v v
[Next.js Build] [Vercel Edge CDN]
| |
v v
[Statische Seiten] <------+
|
v
[Endbenutzer]
WordPress-Hosting#
Empfohlene Hosting-Optionen fuer WordPress als Headless CMS:
- WordPress.com Business -- verwaltetes Hosting, automatische Updates
- Kinsta -- hohe Leistung, PHP 8.x-Unterstuetzung, Staging-Umgebungen
- WP Engine -- dediziertes WordPress-Hosting, erweiterte Tools
- DigitalOcean Droplet -- volle Kontrolle, niedrige Kosten
Next.js auf Vercel bereitstellen#
# Vercel CLI installieren
npm install -g vercel
# Bereitstellen
vercel --prod
# Umgebungsvariablen konfigurieren
vercel env add WORDPRESS_GRAPHQL_URL
vercel env add REVALIDATION_SECRET
vercel env add WORDPRESS_PREVIEW_SECRET
Docker Compose fuer lokale Entwicklung#
# docker-compose.yml
services:
wordpress:
image: wordpress:6.4-php8.2-apache
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: secret
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
- ./wp-plugins:/var/www/html/wp-content/plugins
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: secret
MYSQL_ROOT_PASSWORD: rootsecret
volumes:
- db_data:/var/lib/mysql
nextjs:
build: ./frontend
ports:
- "3000:3000"
environment:
WORDPRESS_GRAPHQL_URL: http://wordpress:80/graphql
depends_on:
- wordpress
volumes:
wp_data:
db_data:
Fazit#
WordPress als Headless CMS in Kombination mit Next.js ist eine leistungsstarke Kombination, die eine vertraute Inhaltsverwaltungsoberflaeche mit einer modernen, hochperformanten Praesentationsschicht vereint. Die wichtigsten Vorteile sind:
- Performance -- statische Seiten ueber CDN bereitgestellt, Ladezeiten unter einer Sekunde
- SEO -- volle Kontrolle ueber Metadaten, serverseitiges Rendering, automatische Sitemaps
- Flexibilitaet -- beliebige Frontend-Technologie, mehrkanalige Inhaltsdistribution
- Sicherheit -- Admin-Panel vom oeffentlichen Auftritt getrennt
- Skalierbarkeit -- Inhalte im CDN, Backend nur zur Bearbeitung
Bei MDS Software Solutions Group sind wir auf den Aufbau moderner Webloesungen basierend auf Headless-Architektur spezialisiert. Wir helfen Unternehmen bei der Migration vom traditionellen WordPress zur Headless-Architektur, entwerfen und implementieren Next.js-Frontends und integrieren CMS-Systeme mit mobilen Anwendungen und anderen Distributionskanaelen.
Benoetigen Sie eine leistungsstarke Website mit WordPress und Next.js? Kontaktieren Sie uns -- unsere Experten helfen Ihnen, die beste Architektur auszuwaehlen und eine auf Ihre Beduerfnisse zugeschnittene Loesung zu liefern.
Team von Programmierexperten, die sich auf moderne Webtechnologien spezialisiert haben.