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

AspectHeadlessDecoupled
FrontendCompletely externalExternal but may still use WP frontend partially
RenderingFully independentHybrid
FlexibilityHighModerate
Use caseSPA / mobile appsProgressive 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();
}