SiamCafe.net Blog
Technology

WordPress Headless Developer Experience DX — สร้าง Frontend ด้วย Next.js

wordpress headless developer experience dx
WordPress Headless Developer Experience DX | SiamCafe Blog
2025-07-14· อ. บอม — SiamCafe.net· 1,496 คำ

Headless WordPress คืออะไร

Headless WordPress เป็นการใช้ WordPress เป็น backend CMS (Content Management System) สำหรับจัดการ content เท่านั้น แล้วใช้ frontend framework อื่น เช่น Next.js, Nuxt.js, Astro หรือ SvelteKit แสดงผล content ผ่าน API แทนที่จะใช้ WordPress theme แบบ traditional

Developer Experience (DX) หมายถึงประสบการณ์ของ developer ในการพัฒนาและดูแลระบบ ครอบคลุม development workflow, tooling, documentation, debugging, deployment process และ code maintainability Headless WordPress ช่วยปรับปรุง DX โดยให้ frontend developers ใช้ modern tools ที่คุ้นเคย (React, TypeScript, Tailwind) แทนที่จะต้องเขียน PHP themes

ข้อดีของ Headless WordPress ได้แก่ Performance frontend เป็น static/SSR เร็วกว่า PHP rendering, Security ลด attack surface เพราะ WordPress ไม่ expose ให้ผู้ใช้โดยตรง, Flexibility ใช้ frontend framework ไหนัก็ได้, Scalability CDN cache static pages ได้ง่าย, DX developer ใช้ modern tooling TypeScript hot reload component libraries

ติดตั้ง Headless WordPress

Setup WordPress เป็น headless CMS

# === Headless WordPress Setup ===

# 1. Install WordPress (Docker)
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  wordpress:
    image: wordpress:6.7-php8.2-apache
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wp
      WORDPRESS_DB_PASSWORD: secretpass
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html

  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp
      MYSQL_PASSWORD: secretpass
      MYSQL_ROOT_PASSWORD: rootpass
    volumes:
      - db_data:/var/lib/mysql

volumes:
  wp_data:
  db_data:
EOF

docker-compose up -d

# 2. Install Required Plugins
# ===================================
# WPGraphQL — GraphQL API for WordPress
wp plugin install wp-graphql --activate

# WPGraphQL for ACF — Custom Fields in GraphQL
wp plugin install wpgraphql-acf --activate

# Advanced Custom Fields PRO — Custom content types
# (install manually or via composer)

# Application Passwords — API authentication
# (built-in since WP 5.6)

# 3. Configure WordPress for Headless
# wp-config.php additions:
cat >> wp-config-extra.php << 'PHPEOF'
 function($post) {
            $img_id = get_post_thumbnail_id($post['id']);
            return $img_id ? wp_get_attachment_image_url($img_id, 'large') : null;
        }
    ]);
});
PHPEOF

# 4. Create Application Password for API
# WordPress Admin > Users > Edit Profile > Application Passwords
# Name: "Frontend App"
# Copy the generated password

echo "Headless WordPress configured"

สร้าง Frontend ด้วย Next.js

สร้าง frontend สำหรับ Headless WordPress

// === Next.js Frontend for Headless WordPress ===

// 1. Create Next.js Project
// npx create-next-app@latest wp-frontend --typescript --tailwind --app

// 2. lib/wordpress.ts — WordPress API Client
// ===================================
const API_URL = process.env.WORDPRESS_API_URL || 'http://localhost:8080';

interface WPPost {
  id: number;
  title: { rendered: string };
  content: { rendered: string };
  excerpt: { rendered: string };
  slug: string;
  date: string;
  featured_image_url: string | null;
  categories: number[];
}

interface WPCategory {
  id: number;
  name: string;
  slug: string;
  count: number;
}

async function fetchAPI(endpoint: string, options: RequestInit = {}) {
  const url = `/wp-json/wp/v2`;
  
  const res = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
    next: { revalidate: 60 }, // ISR: revalidate every 60 seconds
  });
  
  if (!res.ok) {
    throw new Error(`WordPress API error: `);
  }
  
  return res.json();
}

// API Functions
async function getPosts(page = 1, perPage = 10): Promise {
  return fetchAPI(`/posts?page=&per_page=&_embed`);
}

async function getPostBySlug(slug: string): Promise {
  const posts = await fetchAPI(`/posts?slug=wordpress-headless-developer-experience-dx&_embed`);
  return posts[0] || null;
}

async function getCategories(): Promise {
  return fetchAPI('/categories?per_page=100');
}

async function searchPosts(query: string): Promise {
  return fetchAPI(`/posts?search=&per_page=20`);
}

// 3. app/page.tsx — Home Page
// ===================================
// export default async function HomePage() {
//   const posts = await getPosts(1, 12);
//   
//   return (
//     
// //
// {posts.map((post) => ( // // ))} //
//
// ); // } // 4. components/PostCard.tsx // =================================== // function PostCard({ post }: { post: WPPost }) { // return ( // // ); // } console.log("Next.js frontend configured"); console.log("API URL:", API_URL);

WordPress REST API และ GraphQL

ใช้ REST API และ WPGraphQL

# === WordPress API Usage ===

# 1. REST API Examples
# ===================================
# Get all posts
curl -s "http://localhost:8080/wp-json/wp/v2/posts?per_page=10" | python3 -m json.tool

# Get single post by slug
curl -s "http://localhost:8080/wp-json/wp/v2/posts?slug=hello-world" | python3 -m json.tool

# Get posts with embedded data (featured image, author, categories)
curl -s "http://localhost:8080/wp-json/wp/v2/posts?_embed&per_page=5"

# Search posts
curl -s "http://localhost:8080/wp-json/wp/v2/posts?search=wordpress"

# Get categories
curl -s "http://localhost:8080/wp-json/wp/v2/categories"

# Get pages
curl -s "http://localhost:8080/wp-json/wp/v2/pages"

# Get menus (requires WP REST API Menus plugin)
curl -s "http://localhost:8080/wp-json/wp/v2/menus"

# Create post (authenticated)
curl -X POST "http://localhost:8080/wp-json/wp/v2/posts" \
  -H "Content-Type: application/json" \
  -u "admin:app-password-here" \
  -d '{
    "title": "New Post via API",
    "content": "Content created from API",
    "status": "publish"
  }'

# 2. WPGraphQL Queries
# ===================================
# Endpoint: http://localhost:8080/graphql

# Get posts with categories
# query GetPosts {
#   posts(first: 10) {
#     nodes {
#       id
#       title
#       slug
#       date
#       excerpt
#       content
#       featuredImage {
#         node {
#           sourceUrl
#           altText
#         }
#       }
#       categories {
#         nodes {
#           name
#           slug
#         }
#       }
#       author {
#         node {
#           name
#           avatar {
#             url
#           }
#         }
#       }
#     }
#   }
# }

# Get single post
# query GetPost($slug: ID!) {
#   post(id: $slug, idType: SLUG) {
#     title
#     content
#     date
#     seo {
#       title
#       metaDesc
#       opengraphImage {
#         sourceUrl
#       }
#     }
#   }
# }

# 3. GraphQL Client Setup (TypeScript)
# ===================================
cat > lib/graphql.ts << 'TSEOF'
const GRAPHQL_URL = process.env.WORDPRESS_GRAPHQL_URL || 'http://localhost:8080/graphql';

async function fetchGraphQL(query: string, variables: Record = {}) {
  const res = await fetch(GRAPHQL_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables }),
    next: { revalidate: 60 },
  });
  
  const json = await res.json();
  if (json.errors) {
    console.error('GraphQL errors:', json.errors);
    throw new Error('GraphQL query failed');
  }
  
  return json.data;
}

export { fetchGraphQL };
TSEOF

echo "API configuration complete"

Developer Experience Optimization

ปรับปรุง DX สำหรับ Headless WordPress

#!/usr/bin/env python3
# dx_optimization.py — Developer Experience Tools
import json
import logging
from typing import Dict, List

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("dx")

class DXOptimizer:
    def __init__(self):
        self.checks = []
    
    def evaluate_dx(self):
        """Evaluate Developer Experience score"""
        categories = {
            "setup_time": {
                "score": 8,
                "max": 10,
                "details": "Docker Compose setup < 5 minutes",
                "improvements": ["Add make commands for common tasks"],
            },
            "type_safety": {
                "score": 9,
                "max": 10,
                "details": "TypeScript + WPGraphQL codegen",
                "improvements": ["Generate types from GraphQL schema"],
            },
            "hot_reload": {
                "score": 9,
                "max": 10,
                "details": "Next.js Fast Refresh + WordPress live preview",
                "improvements": ["Add WordPress preview mode for drafts"],
            },
            "testing": {
                "score": 7,
                "max": 10,
                "details": "Jest + Playwright + MSW for API mocking",
                "improvements": ["Add visual regression tests", "Mock WP API in tests"],
            },
            "documentation": {
                "score": 6,
                "max": 10,
                "details": "README + Storybook for components",
                "improvements": ["Add API documentation", "Add architecture diagrams"],
            },
            "deployment": {
                "score": 8,
                "max": 10,
                "details": "Vercel preview deploys + GitHub Actions",
                "improvements": ["Add staging environment", "Automated E2E tests"],
            },
            "debugging": {
                "score": 7,
                "max": 10,
                "details": "React DevTools + Network inspector",
                "improvements": ["Add error boundary logging", "GraphQL debugging tools"],
            },
        }
        
        total_score = sum(c["score"] for c in categories.values())
        total_max = sum(c["max"] for c in categories.values())
        
        return {
            "overall_score": round(total_score / total_max * 100, 1),
            "categories": categories,
            "top_improvements": [
                imp
                for cat in categories.values()
                for imp in cat["improvements"][:1]
            ],
        }
    
    def recommended_tooling(self):
        return {
            "frontend": {
                "framework": "Next.js 15 (App Router)",
                "language": "TypeScript 5.x",
                "styling": "Tailwind CSS 4.x",
                "components": "shadcn/ui",
                "icons": "Lucide React",
                "forms": "React Hook Form + Zod",
            },
            "api": {
                "client": "WPGraphQL + graphql-request",
                "codegen": "@graphql-codegen/cli",
                "caching": "Next.js ISR + SWR for client",
                "preview": "WordPress Preview Mode",
            },
            "testing": {
                "unit": "Vitest",
                "component": "Testing Library",
                "e2e": "Playwright",
                "api_mock": "MSW (Mock Service Worker)",
            },
            "devops": {
                "hosting": "Vercel (frontend) + WP Engine (WordPress)",
                "ci_cd": "GitHub Actions",
                "monitoring": "Vercel Analytics + Sentry",
                "cms_preview": "Vercel Preview + WP Preview",
            },
        }

dx = DXOptimizer()
evaluation = dx.evaluate_dx()
print("DX Score:", json.dumps({"score": evaluation["overall_score"]}, indent=2))
print("Top Improvements:", json.dumps(evaluation["top_improvements"], indent=2))

tooling = dx.recommended_tooling()
print("Tooling:", json.dumps(tooling["frontend"], indent=2))

Deployment และ Performance

Deploy Headless WordPress

# === Deployment Configuration ===

# 1. Vercel Deployment (Frontend)
cat > vercel.json << 'EOF'
{
  "framework": "nextjs",
  "buildCommand": "next build",
  "env": {
    "WORDPRESS_API_URL": "@wordpress-api-url",
    "WORDPRESS_GRAPHQL_URL": "@wordpress-graphql-url",
    "REVALIDATE_SECRET": "@revalidate-secret"
  },
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "X-Frame-Options", "value": "DENY" }
      ]
    }
  ]
}
EOF

# 2. On-Demand Revalidation (ISR)
# When WordPress content changes, trigger revalidation
# WordPress webhook plugin calls:
# POST https://frontend.example.com/api/revalidate
# Body: { "secret": "xxx", "slug": "post-slug" }

# 3. next.config.js
cat > next.config.js << 'EOF'
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'wp.example.com',
        pathname: '/wp-content/uploads/**',
      },
    ],
  },
  
  async rewrites() {
    return [
      {
        source: '/sitemap.xml',
        destination: '/api/sitemap',
      },
    ];
  },
};

module.exports = nextConfig;
EOF

# 4. Performance Optimization
# ===================================
# - ISR (Incremental Static Regeneration): revalidate every 60s
# - Image optimization via next/image
# - Font optimization via next/font
# - Route prefetching
# - Static generation for known slugs (generateStaticParams)
# - CDN caching at Vercel edge

# 5. GitHub Actions CI/CD
cat > .github/workflows/deploy.yml << 'EOF'
name: Deploy Frontend
on:
  push:
    branches: [main]
  repository_dispatch:
    types: [wordpress_update]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - run: npm ci
      - run: npm run lint
      - run: npm run test
      - run: npm run build
        env:
          WORDPRESS_API_URL: }
      
      - uses: amondnet/vercel-action@v25
        with:
          vercel-token: }
          vercel-org-id: }
          vercel-project-id: }
          vercel-args: '--prod'
EOF

echo "Deployment configured"

FAQ คำถามที่พบบ่อย

Q: Headless WordPress เหมาะกับ project แบบไหน?

A: เหมาะกับ content-heavy sites ที่ต้องการ performance สูง (blog, news, magazine), multi-channel content delivery (website + mobile app + kiosk), sites ที่ต้องการ custom frontend (complex interactions, animations), teams ที่มี frontend developers ชำนาญ React/Vue ไม่เหมาะกับ simple brochure sites, projects ที่ต้องการใช้ WordPress plugins หลายตัว (เช่น WooCommerce, Elementor), teams ที่ไม่มี frontend expertise, projects ที่ budget จำกัด (ต้อง maintain 2 systems)

Q: WPGraphQL กับ REST API เลือกใช้อย่างไร?

A: WPGraphQL ข้อดี query เฉพาะ fields ที่ต้องการ (ไม่ over-fetch), nested data ในรอบเดียว (post + author + categories), type-safe codegen, GraphiQL playground สำหรับ explore schema REST API ข้อดี built-in ไม่ต้องติดตั้ง plugin เพิ่ม, familiar สำหรับ developers ทั่วไป, caching ง่ายกว่า (GET requests) แนะนำ WPGraphQL สำหรับ complex frontends ที่ต้องการหลาย data types REST API สำหรับ simple integrations

Q: Preview mode ทำงานอย่างไร?

A: WordPress Preview Mode ให้ editors เห็น draft content บน frontend ก่อน publish ขั้นตอน editor คลิก Preview ใน WordPress, WordPress redirect ไปยัง frontend URL พร้อม preview token, frontend ตรวจสอบ token แล้ว fetch draft content จาก WordPress API, แสดง draft content ให้ editor เห็น (ไม่แสดงให้ public) Next.js รองรับ Draft Mode built-in ใช้ cookies สำหรับ identify preview sessions ต้อง configure CORS และ authentication ให้ถูกต้อง

Q: ค่าใช้จ่ายของ Headless WordPress เท่าไหร?

A: WordPress hosting managed hosting เช่น WP Engine $20-50/month (ไม่ต้อง handle traffic เพราะ frontend แยก) หรือ VPS $5-20/month Frontend hosting Vercel free tier รองรับ 100GB bandwidth/month, Netlify free tier 100GB, Cloudflare Pages unlimited bandwidth ฟรี รวมค่าใช้จ่ายเริ่มต้น $5-25/month สำหรับ small-medium sites ถูกกว่า traditional WordPress ที่ต้อง scale server เมื่อ traffic สูง เพราะ frontend serve จาก CDN

📖 บทความที่เกี่ยวข้อง

PagerDuty Incident Developer Experience DXอ่านบทความ → Ansible Collection Developer Experience DXอ่านบทความ → WordPress Headless Security Hardening ป้องกันแฮกอ่านบทความ → WordPress Headless Agile Scrum Kanbanอ่านบทความ → Go Fiber Developer Experience DXอ่านบทความ →

📚 ดูบทความทั้งหมด →