WordPress Headless CMS
WordPress Headless คือการใช้ WordPress เป็น Content Management System ที่ให้ข้อมูลผ่าน API เท่านั้น ไม่ใช้ Theme แสดงผล Frontend สร้างด้วย Framework สมัยใหม่เช่น Next.js, Nuxt.js หรือ Astro ทำให้เว็บเร็วขึ้นมากและ Scale ได้ดี
ในระบบ Distributed กระจาย WordPress ข้าม Regions, Database Replication, CDN, Redis Cache ทำให้ระบบ High Availability และเร็วสำหรับผู้ใช้ทั่วโลก
WordPress Headless Setup
# === WordPress Headless Setup ===
# 1. ติดตั้ง WordPress + WPGraphQL
# docker-compose.yml
# version: '3.8'
# services:
# wordpress:
# image: wordpress:6.5-php8.3-apache
# ports:
# - "8080:80"
# environment:
# WORDPRESS_DB_HOST: mysql
# WORDPRESS_DB_NAME: wordpress
# WORDPRESS_DB_USER: wp
# WORDPRESS_DB_PASSWORD: SecurePass123
# WORDPRESS_CONFIG_EXTRA: |
# define('WP_HOME', 'https://cms.example.com');
# define('WP_SITEURL', 'https://cms.example.com');
# define('HEADLESS_MODE', true);
# define('GRAPHQL_JWT_AUTH_SECRET_KEY', 'your-secret-key');
# volumes:
# - wp-data:/var/www/html
# depends_on:
# - mysql
# - redis
#
# mysql:
# image: mysql:8.0
# environment:
# MYSQL_DATABASE: wordpress
# MYSQL_USER: wp
# MYSQL_PASSWORD: SecurePass123
# MYSQL_ROOT_PASSWORD: RootPass123
# volumes:
# - db-data:/var/lib/mysql
# command: >
# --default-authentication-plugin=mysql_native_password
# --innodb-buffer-pool-size=256M
# --max-connections=200
#
# redis:
# image: redis:7-alpine
# command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
#
# volumes:
# wp-data:
# db-data:
# 2. ติดตั้ง Plugins
# wp plugin install wp-graphql --activate
# wp plugin install wp-graphql-jwt-authentication --activate
# wp plugin install redis-cache --activate
# 3. Headless Mode (functions.php)
# // Redirect frontend to API
# add_action('template_redirect', function() {
# if (!defined('HEADLESS_MODE') || !HEADLESS_MODE) return;
# if (is_admin() || wp_doing_ajax() || wp_doing_cron()) return;
# if (strpos($_SERVER['REQUEST_URI'], '/graphql') !== false) return;
# if (strpos($_SERVER['REQUEST_URI'], '/wp-json') !== false) return;
# wp_redirect('https://www.example.com', 301);
# exit;
# });
# 4. CORS Headers
# // Allow frontend domain
# add_action('init', function() {
# header('Access-Control-Allow-Origin: https://www.example.com');
# header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
# header('Access-Control-Allow-Headers: Content-Type, Authorization');
# });
# 5. ทดสอบ GraphQL
# curl -X POST https://cms.example.com/graphql \
# -H "Content-Type: application/json" \
# -d '{"query": "{ posts { nodes { title slug excerpt date } } }"}'
echo "WordPress Headless CMS configured"
echo " REST API: https://cms.example.com/wp-json/wp/v2/"
echo " GraphQL: https://cms.example.com/graphql"
Next.js Frontend สำหรับ Headless WordPress
// === Next.js Frontend สำหรับ Headless WordPress ===
// npx create-next-app@latest frontend --typescript --tailwind
// lib/wordpress.ts — WordPress API Client
const API_URL = process.env.WORDPRESS_GRAPHQL_URL || "https://cms.example.com/graphql";
interface Post {
title: string;
slug: string;
excerpt: string;
date: string;
content: string;
featuredImage?: {
node: { sourceUrl: string; altText: string };
};
categories: { nodes: { name: string; slug: string }[] };
}
async function fetchGraphQL(query: string, variables?: Record<string, any>) {
const res = await fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
next: { revalidate: 60 }, // ISR: Revalidate ทุก 60 วินาที
});
if (!res.ok) throw new Error(`GraphQL error: `);
const json = await res.json();
if (json.errors) throw new Error(json.errors[0].message);
return json.data;
}
// ดึง Posts ทั้งหมด
export async function getPosts(first = 20): Promise<Post[]> {
const data = await fetchGraphQL(`
query GetPosts($first: Int!) {
posts(first: $first, where: { status: PUBLISH }) {
nodes {
title
slug
excerpt
date
featuredImage {
node { sourceUrl altText }
}
categories {
nodes { name slug }
}
}
}
}
`, { first });
return data.posts.nodes;
}
// ดึง Post เฉพาะ
export async function getPost(slug: string): Promise<Post> {
const data = await fetchGraphQL(`
query GetPost($slug: ID!) {
post(id: $slug, idType: SLUG) {
title
slug
content
date
excerpt
featuredImage {
node { sourceUrl altText }
}
categories {
nodes { name slug }
}
}
}
`, { slug });
return data.post;
}
// ดึง Slugs สำหรับ Static Generation
export async function getAllSlugs(): Promise<string[]> {
const data = await fetchGraphQL(`
query GetSlugs {
posts(first: 1000, where: { status: PUBLISH }) {
nodes { slug }
}
}
`);
return data.posts.nodes.map((p: { slug: string }) => p.slug);
}
// === app/page.tsx — Homepage ===
// export default async function Home() {
// const posts = await getPosts(12);
// return (
// <main className="max-w-6xl mx-auto px-4 py-8">
// <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) => (
// <PostCard key={post.slug} post={post} />
// ))}
// </div>
// </main>
// );
// }
// === app/blog/[slug]/page.tsx — Single Post ===
// export async function generateStaticParams() {
// const slugs = await getAllSlugs();
// return slugs.map((slug) => ({ slug }));
// }
//
// export default async function BlogPost({ params }) {
// const post = await getPost(params.slug);
// return (
// <article className="max-w-3xl mx-auto px-4 py-8">
// <h1 className="text-4xl font-bold">{post.title}</h1>
// <div dangerouslySetInnerHTML={{ __html: post.content }} />
// </article>
// );
// }
console.log("Next.js Headless WordPress Frontend");
console.log(" ISR: Revalidate every 60 seconds");
console.log(" SSG: Pre-render all posts at build time");
Distributed Architecture
# === Distributed WordPress Architecture ===
# 1. MySQL Replication (Primary-Replica)
# Primary: Write Queries (INSERT, UPDATE, DELETE)
# Replica: Read Queries (SELECT) — สำหรับ GraphQL Queries
# my.cnf (Primary)
# [mysqld]
# server-id = 1
# log-bin = mysql-bin
# binlog-format = ROW
# gtid-mode = ON
# enforce-gtid-consistency = ON
# my.cnf (Replica)
# [mysqld]
# server-id = 2
# relay-log = mysql-relay
# read-only = ON
# gtid-mode = ON
# enforce-gtid-consistency = ON
# 2. Redis Cluster สำหรับ Object Cache
# redis-cluster.conf
# cluster-enabled yes
# cluster-config-file nodes.conf
# cluster-node-timeout 5000
# appendonly yes
# redis-cli --cluster create \
# node1:6379 node2:6379 node3:6379 \
# node4:6379 node5:6379 node6:6379 \
# --cluster-replicas 1
# 3. S3-compatible Object Storage สำหรับ Media
# wp-config.php
# define('AS3CF_SETTINGS', serialize(array(
# 'provider' => 'aws',
# 'access-key-id' => 'YOUR_KEY',
# 'secret-access-key' => 'YOUR_SECRET',
# 'bucket' => 'wp-media-bucket',
# 'region' => 'ap-southeast-1',
# 'copy-to-s3' => true,
# 'serve-from-s3' => true,
# 'remove-local-file' => true,
# )));
# 4. Nginx Load Balancer
# upstream wordpress {
# least_conn;
# server wp-node1:8080 weight=5;
# server wp-node2:8080 weight=5;
# server wp-node3:8080 weight=3; # Smaller instance
# }
#
# server {
# listen 443 ssl http2;
# server_name cms.example.com;
#
# # GraphQL Endpoint — Cache 60s
# location /graphql {
# proxy_pass http://wordpress;
# proxy_cache wp_cache;
# proxy_cache_valid 200 60s;
# proxy_cache_key "$request_body";
# proxy_cache_methods POST;
# add_header X-Cache $upstream_cache_status;
# }
#
# # REST API — Cache 60s
# location /wp-json/ {
# proxy_pass http://wordpress;
# proxy_cache wp_cache;
# proxy_cache_valid 200 60s;
# }
#
# # Admin — No Cache
# location /wp-admin {
# proxy_pass http://wordpress;
# }
# }
# 5. CDN สำหรับ Frontend
# Vercel, Cloudflare Pages, Netlify
# Frontend Deploy เป็น Static/ISR บน CDN
# ผู้ใช้โหลดจาก Edge ใกล้ที่สุด
# WordPress API ถูกเรียกตอน Revalidation เท่านั้น
print("Distributed WordPress Architecture:")
print(" WordPress (3 nodes) -> Nginx LB")
print(" MySQL Primary -> 2 Replicas")
print(" Redis Cluster (6 nodes)")
print(" S3 Object Storage for Media")
print(" CDN for Frontend (Next.js ISR)")
print(" GraphQL Cache at Nginx (60s TTL)")
Best Practices
- ISR (Incremental Static Regeneration): ใช้ ISR ใน Next.js Revalidate ทุก 60 วินาที ไม่ต้อง Rebuild ทั้งเว็บ
- GraphQL over REST: ใช้ WPGraphQL ดึงข้อมูลตรงตามต้องการ ลด Over-fetching
- Cache GraphQL: Cache GraphQL Responses ที่ Nginx หรือ CDN ลด Load บน WordPress
- Database Replication: แยก Read/Write ส่ง GraphQL Queries ไป Replica
- Media on S3: เก็บ Media Files บน S3 ส่งผ่าน CDN ไม่ใช้ Disk บน WordPress
- Webhook Revalidation: ใช้ Webhook เมื่อ Publish/Update Post เรียก Revalidation บน Next.js
WordPress Headless คืออะไร
ใช้ WordPress เป็น Backend CMS ไม่ใช้ Theme แสดงผล ใช้ REST API หรือ WPGraphQL ส่งข้อมูลไป Frontend ที่สร้างด้วย Next.js Vue Nuxt แยก Backend กับ Frontend
Distributed System กับ WordPress ใช้อย่างไร
กระจาย WordPress ข้าม Regions Database Replication (Primary-Replica) Object Storage (S3) Media CDN Static Assets Redis Cluster Object Cache Load Balancer กระจาย Traffic
WPGraphQL คืออะไร
Plugin เพิ่ม GraphQL API ให้ WordPress ดึงข้อมูลตรงตามต้องการ ไม่มี Over-fetching Posts Pages Custom Post Types ผ่าน Query เดียว เร็วกว่า REST API สำหรับ Complex Queries
Headless WordPress เร็วกว่าปกติหรือไม่
เร็วกว่ามาก Frontend เป็น Static/ISR Pre-render HTML ไม่ต้องรอ PHP/MySQL ทุก Request WordPress เป็นแค่ API Server ใช้ Cache เต็มที่ Frontend บน CDN โหลดเร็วทั่วโลก
สรุป
WordPress Headless ร่วมกับ Distributed System ให้เว็บที่เร็วและ Scale ได้ดี ใช้ WPGraphQL ดึงข้อมูล Next.js ISR สำหรับ Frontend Deploy บน CDN Database Replication แยก Read/Write Redis Cluster สำหรับ Cache S3 สำหรับ Media Nginx Cache GraphQL Responses Webhook Revalidation เมื่อ Content เปลี่ยน
