Decoupled WordPress (Advanced Notes)
Concept
A decoupled (headless) WordPress architecture separates:
Backend (WordPress CMS) → content management
Frontend (React / Next.js / other) → presentation layer
Architecture Flow
WordPress (REST API / GraphQL)
↓
API Layer (JSON)
↓
Frontend (React / Next.js)
Headless vs Decoupled
| Aspect | Headless | Decoupled |
|---|---|---|
| Frontend | Completely external | External but may still use WP frontend partially |
| Rendering | Fully independent | Hybrid |
| Flexibility | High | Moderate |
| Use case | SPA / mobile apps | Progressive migration |
Enabling API in WordPress
REST API (built-in)
Example endpoint:
https://example.com/wp-json/wp/v2/posts
Fetch Example (JavaScript)
async function getPosts() {
const res = await fetch('https://example.com/wp-json/wp/v2/posts');
const data = await res.json();
return data;
}
GraphQL (via WPGraphQL plugin)
Query:
{
posts {
nodes {
id
title
content
}
}
}
Fetch Example:
async function fetchGraphQL() {
const res = await fetch('https://example.com/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
{
posts {
nodes {
title
content
}
}
}
`
})
});
const json = await res.json();
return json.data.posts.nodes;
}
Authentication (JWT Example)
// Install JWT Auth plugin in WordPress
POST /wp-json/jwt-auth/v1/token
async function login() {
const res = await fetch('https://example.com/wp-json/jwt-auth/v1/token', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: 'admin',
password: 'password'
})
});
const data = await res.json();
return data.token;
}
React Fundamentals
Component Example
function Post({ title, content }) {
return (
<article>
<h2>{title}</h2>
<div dangerouslySetInnerHTML={{ __html: content }} />
</article>
);
}
State and Effects (Hooks)
import { useState, useEffect } from 'react';
function Posts() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch('https://example.com/wp-json/wp/v2/posts')
.then(res => res.json())
.then(data => setPosts(data));
}, []);
return (
<div>
{posts.map(post => (
<Post
key={post.id}
title={post.title.rendered}
content={post.content.rendered}
/>
))}
</div>
);
}
Event Handling
function Button() {
const handleClick = () => {
console.log('Clicked');
};
return <button onClick={handleClick}>Click Me</button>;
}
Project Setup
npx create-next-app wp-headless
cd wp-headless
npm run dev
Fetch WordPress Data (SSG)
// pages/index.js
export async function getStaticProps() {
const res = await fetch('https://example.com/wp-json/wp/v2/posts');
const posts = await res.json();
return {
props: { posts },
revalidate: 60
};
}
export default function Home({ posts }) {
return (
<div>
{posts.map(post => (
<h2 key={post.id}>{post.title.rendered}</h2>
))}
</div>
);
}
Dynamic Routing
// pages/posts/[slug].js
export async function getStaticPaths() {
const res = await fetch('https://example.com/wp-json/wp/v2/posts');
const posts = await res.json();
const paths = posts.map(post => ({
params: { slug: post.slug }
}));
return { paths, fallback: 'blocking' };
}
export async function getStaticProps({ params }) {
const res = await fetch(
`https://example.com/wp-json/wp/v2/posts?slug=${params.slug}`
);
const data = await res.json();
return {
props: { post: data[0] }
};
}
export default function PostPage({ post }) {
return (
<article>
<h1>{post.title.rendered}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
</article>
);
}
API Routes (Middleware Layer)
// pages/api/posts.js
export default async function handler(req, res) {
const response = await fetch('https://example.com/wp-json/wp/v2/posts');
const data = await response.json();
res.status(200).json(data);
}
Image Optimization
import Image from 'next/image';
<Image
src="/hero.jpg"
width={800}
height={400}
alt="Hero"
/>
Incremental Static Regeneration
export async function getStaticProps() {
return {
props: {},
revalidate: 10
};
}
Practical Architecture Example
Stack
WordPress → CMS
REST API / GraphQL → Data layer
Next.js → Frontend
CDN → Performance layer
Folder Structure
/pages
index.js
/posts
[slug].js
/components
Post.js
/lib
api.js
API Utility Layer
// lib/api.js
const API_URL = 'https://example.com/wp-json/wp/v2';
export async function fetchPosts() {
const res = await fetch(`${API_URL}/posts`);
return res.json();
}