WordPress Headless CMS คืออะไร
WordPress Headless CMS คือการใช้ WordPress เป็น Backend สำหรับจัดการ Content เพียงอย่างเดียว โดยไม่ใช้ PHP Theme ของ WordPress ในการแสดงผล แต่ใช้ Frontend Framework อย่าง Next.js, Nuxt.js, Gatsby หรือ Astro เชื่อมต่อผ่าน REST API หรือ WPGraphQL เพื่อดึง Content มาแสดงผลแทน
สถาปัตยกรรมนี้แยก Backend (Content Management) ออกจาก Frontend (Presentation) อย่างชัดเจน ทำให้แต่ละส่วนสามารถ Scale, Deploy และ Maintain ได้อิสระจากกัน Frontend สามารถใช้ Static Site Generation (SSG) หรือ Incremental Static Regeneration (ISR) เพื่อ Performance ที่ดีเยี่ยม ขณะที่ Backend ยังใช้ WordPress Dashboard ที่ Content Team คุ้นเคย
การตั้งค่า WordPress สำหรับ Headless Mode
# wp-config.php — Configuration สำหรับ Headless Mode
// ปิด Frontend ของ WordPress (Redirect ไป Admin เท่านั้น)
define('WP_HOME', 'https://cms.example.com');
define('WP_SITEURL', 'https://cms.example.com');
// เพิ่ม Security Headers
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', false);
// Cache Configuration
define('WP_CACHE', true);
define('WP_REDIS_HOST', getenv('REDIS_HOST') ?: '127.0.0.1');
define('WP_REDIS_PORT', getenv('REDIS_PORT') ?: 6379);
// Memory Limit
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
// REST API Configuration
define('REST_API_MAX_RESULTS', 100);
---
# functions.php — Custom REST API Endpoints และ CORS
// เปิด CORS สำหรับ Frontend Domain
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://example.com',
'https://www.example.com',
'http://localhost:3000', // Development
];
if (in_array($origin, $allowed_origins)) {
header("Access-Control-Allow-Origin: " . $origin);
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Authorization, Content-Type");
header("Access-Control-Allow-Credentials: true");
}
return $value;
});
});
// Custom REST API Endpoint สำหรับ Popular Posts
add_action('rest_api_init', function() {
register_rest_route('custom/v1', '/popular-posts', [
'methods' => 'GET',
'callback' => function($request) {
$posts = get_posts([
'meta_key' => 'post_views_count',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'posts_per_page' => $request->get_param('per_page') ?: 10,
]);
$data = array_map(function($post) {
return [
'id' => $post->ID,
'title' => $post->post_title,
'slug' => $post->post_name,
'excerpt' => wp_trim_words($post->post_content, 30),
'date' => $post->post_date,
'views' => get_post_meta($post->ID, 'post_views_count', true),
'featured' => get_the_post_thumbnail_url($post->ID, 'large'),
];
}, $posts);
return rest_ensure_response($data);
},
'permission_callback' => '__return_true',
]);
});
// Webhook แจ้ง Frontend เมื่อ Content เปลี่ยน
add_action('save_post', function($post_id, $post) {
if ($post->post_status !== 'publish') return;
$webhook_url = getenv('FRONTEND_REVALIDATE_WEBHOOK');
if (!$webhook_url) return;
wp_remote_post($webhook_url, [
'body' => json_encode([
'type' => 'post_updated',
'id' => $post_id,
'slug' => $post->post_name,
'secret' => getenv('REVALIDATE_SECRET'),
]),
'headers' => ['Content-Type' => 'application/json'],
'timeout' => 10,
]);
}, 10, 2);
Next.js Frontend สำหรับ WordPress Headless
// lib/wordpress.ts — WordPress API Client
const WP_API_URL = process.env.WORDPRESS_API_URL || 'https://cms.example.com/wp-json';
interface WPPost {
id: number;
slug: string;
title: { rendered: string };
content: { rendered: string };
excerpt: { rendered: string };
date: string;
featured_media: number;
_embedded?: {
'wp:featuredmedia'?: Array<{ source_url: string; alt_text: string }>;
};
}
export async function getPosts(page = 1, perPage = 10): Promise<WPPost[]> {
const res = await fetch(
`/wp/v2/posts?page=&per_page=&_embed`,
{
next: { revalidate: 300 }, // ISR: Revalidate ทุก 5 นาที
headers: { 'Accept': 'application/json' },
}
);
if (!res.ok) throw new Error(`WP API Error: `);
return res.json();
}
export async function getPostBySlug(slug: string): Promise<WPPost | null> {
const res = await fetch(
`/wp/v2/posts?slug=wordpress-headless-post-mortem-analysis&_embed`,
{
next: { revalidate: 60 },
headers: { 'Accept': 'application/json' },
}
);
if (!res.ok) return null;
const posts = await res.json();
return posts[0] || null;
}
// app/api/revalidate/route.ts — On-demand Revalidation
import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
if (body.secret !== process.env.REVALIDATE_SECRET) {
return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
}
if (body.type === 'post_updated' && body.slug) {
revalidatePath(`/blog/`);
revalidatePath('/blog');
return NextResponse.json({ revalidated: true, slug: body.slug });
}
return NextResponse.json({ error: 'Unknown type' }, { status: 400 });
}
Post-mortem Analysis — ตัวอย่างจากเหตุการณ์จริง
ต่อไปนี้เป็นตัวอย่าง Post-mortem จากเหตุการณ์ที่ WordPress Headless ล่มเนื่องจาก REST API ตอบสนองช้าจนทำให้ Frontend Build ล้มเหลว
# Post-mortem: WordPress Headless API Timeout
# Date: 2026-02-20
# Duration: 45 นาที
# Severity: SEV-2
# Impact: Frontend แสดงข้อมูลเก่า (Stale Content) เป็นเวลา 45 นาที
## Timeline (UTC+7)
- 09:00 - Content Team Publish บทความ 15 บทความพร้อมกัน (Bulk Import)
- 09:01 - save_post Hook ส่ง Revalidation Webhook 15 ครั้งพร้อมกัน
- 09:02 - Next.js ISR Revalidation เริ่มดึงข้อมูลจาก WP REST API
- 09:03 - WP REST API Response Time เพิ่มจาก 200ms เป็น 8000ms
- 09:05 - MySQL Max Connections ถูกใช้จนเต็ม (151/151)
- 09:06 - Next.js Fetch Timeout — Serve Stale Content แทน
- 09:10 - Prometheus Alert: wp_api_response_time > 5000ms
- 09:12 - PagerDuty Incident Created → On-call Acknowledge
- 09:20 - Root Cause ระบุ: MySQL Connection Pool เต็ม
- 09:25 - เพิ่ม max_connections เป็น 300
- 09:30 - Restart PHP-FPM และ Redis
- 09:35 - WP REST API Response Time กลับสู่ปกติ
- 09:45 - ISR Revalidation สำเร็จ — Content ใหม่แสดงผล
## Root Cause
Content Team ทำ Bulk Import 15 บทความพร้อมกัน ทำให้ save_post Hook
ส่ง Revalidation Webhook 15 ครั้งพร้อมกัน Next.js ISR ดึงข้อมูลจาก
WP REST API 15 Request พร้อมกัน แต่ละ Request ทำ Complex Query
หลายตัว (Posts + Categories + Tags + Featured Media) ทำให้ MySQL
Connection Pool (default 151) ถูกใช้จนเต็ม
## Action Items
- [x] เพิ่ม MySQL max_connections เป็น 300
- [x] ใส่ Debounce ใน Revalidation Webhook (รอ 30 วินาทีก่อนส่ง)
- [x] เพิ่ม Redis Object Cache สำหรับ WP REST API
- [ ] ตั้ง Rate Limit สำหรับ Revalidation Endpoint
- [ ] เพิ่ม Circuit Breaker ใน Next.js API Client
- [ ] สร้าง Load Test สำหรับ Bulk Publish Scenario
การตั้งค่า Monitoring สำหรับ WordPress Headless
# docker-compose.monitoring.yml
version: "3.8"
services:
prometheus:
image: prom/prometheus:v2.50.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:10.3.0
ports:
- "3001:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
wp-exporter:
image: ghcr.io/aorfanos/wordpress-exporter:latest
environment:
- WORDPRESS_URL=https://cms.example.com
- WORDPRESS_USER=monitoring
- WORDPRESS_PASSWORD=
ports:
- "9850:9850"
blackbox:
image: prom/blackbox-exporter:v0.24.0
ports:
- "9115:9115"
---
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'wordpress'
static_configs:
- targets: ['wp-exporter:9850']
- job_name: 'api-probe'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://cms.example.com/wp-json/wp/v2/posts?per_page=1
- https://cms.example.com/wp-json/custom/v1/popular-posts
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox:9115
- job_name: 'nextjs'
static_configs:
- targets: ['frontend:3000']
metrics_path: /api/metrics
---
# Alert Rules
groups:
- name: wordpress_headless
rules:
- alert: WPAPISlowResponse
expr: probe_http_duration_seconds{job="api-probe"} > 3
for: 2m
labels:
severity: warning
annotations:
summary: "WP REST API ตอบช้า {{ $value }}s"
- alert: WPAPIDown
expr: probe_success{job="api-probe"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "WP REST API ไม่ตอบสนองสำหรับ {{ $labels.instance }}"
- alert: WPHighMemoryUsage
expr: wordpress_memory_usage_bytes / wordpress_memory_limit_bytes > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "WordPress Memory Usage สูง {{ $value | humanizePercentage }}"
Best Practices สำหรับ WordPress Headless
- ใช้ Redis Object Cache: ติดตั้ง Redis Object Cache Plugin เพื่อลด Database Query ของ WP REST API สามารถลด Response Time ได้ 50-80%
- ตั้ง Rate Limit บน API: ใช้ Nginx Rate Limiting หรือ Plugin เพื่อป้องกัน API ถูก Flood จาก Revalidation หรือ Bot
- ใช้ ISR แทน SSR: Next.js ISR ช่วยให้ Frontend แสดงผล Static Page ที่ Revalidate เป็นระยะ ลด Load บน WordPress API
- ทำ Debounce สำหรับ Webhook: เมื่อ Content Team Publish หลายบทความพร้อมกัน ควร Debounce Webhook ให้ส่งครั้งเดียวหลังจากไม่มี Activity 30 วินาที
- แยก Read/Write Database: ใช้ MySQL Read Replica สำหรับ REST API Query เพื่อไม่ให้กระทบ Admin Dashboard
- Monitor ทุก Layer: ต้อง Monitor ทั้ง WordPress Backend, Database, Redis, Frontend Build/Deploy และ CDN
- ทำ Post-mortem ทุกครั้งที่มี Incident: บันทึก Timeline, Root Cause และ Action Items เพื่อป้องกันปัญหาซ้ำ
WordPress Headless คืออะไรและต่างจาก WordPress ปกติอย่างไร
WordPress Headless ใช้ WordPress เป็น Backend จัดการ Content ผ่าน REST API หรือ GraphQL แล้วใช้ Frontend Framework อย่าง Next.js แสดงผล ต่างจาก WordPress ปกติที่ใช้ PHP Theme แสดงผลทั้งหมด ข้อดีคือ Performance ดีกว่า, Frontend ยืดหยุ่นกว่า และ Scale ง่ายกว่าเพราะแยก Backend กับ Frontend ออกจากกัน
Post-mortem Analysis คืออะไรและทำไมต้องทำ
Post-mortem Analysis คือการวิเคราะห์เหตุการณ์หลัง Incident เพื่อหา Root Cause และกำหนดมาตรการป้องกัน เป็น Blameless Process ที่ Focus ที่ระบบไม่ใช่ตัวบุคคล ช่วยให้ทีมเรียนรู้จากข้อผิดพลาดและทำให้ระบบ Reliable มากขึ้นในระยะยาว
ปัญหาที่พบบ่อยของ WordPress Headless มีอะไรบ้าง
ปัญหาที่พบบ่อยคือ REST API ตอบช้าเมื่อมี Content มาก, Cache Invalidation ไม่ทำงาน, CORS Error เมื่อ Frontend กับ Backend อยู่คนละ Domain, Authentication Token หมดอายุ, Plugin Update ทำให้ API Format เปลี่ยน และ MySQL Connection Pool เต็มเมื่อมี Request พร้อมกันมากๆ
ควรตั้ง Monitoring อะไรบ้างสำหรับ WordPress Headless
ควร Monitor API Response Time, Error Rate (4xx/5xx), Cache Hit Ratio ของ Redis, PHP Memory Usage, MySQL Query Time และ Connection Count, Frontend Build Time, CDN Cache Status และ ISR Revalidation Success Rate ใช้ Prometheus + Grafana สำหรับ Metrics และ Sentry สำหรับ Error Tracking
สรุปและแนวทางปฏิบัติ
WordPress Headless เป็นสถาปัตยกรรมที่ทรงพลังสำหรับ Content-driven Website แต่มีความซับซ้อนมากกว่า WordPress ปกติเพราะต้องดูแลทั้ง Backend API และ Frontend Application แยกกัน การมี Monitoring ที่ครอบคลุมทุก Layer, Redis Cache สำหรับ API Performance, Debounce สำหรับ Revalidation Webhook และ Post-mortem Process ที่เป็นระบบ จะช่วยให้ระบบมีเสถียรภาพสูงและตอบสนองต่อ Incident ได้รวดเร็ว
