How to Use WordPress as a Headless CMS with Front-end Frameworks?

Ever felt that WordPress themes are limiting your creativity? You’re not alone. While WordPress is fantastic for content management, its front-end can sometimes hold you back—especially when you want cutting-edge performance, advanced animations, or interactive UIs.

Enter Headless WordPress.

headless wordpress

By decoupling WordPress’s back-end (your content management system) from the front-end (the user interface), you gain total control. You can pair WordPress with powerful frameworks like React, Vue, or Svelte. Your website can become lightning-fast, secure, and endlessly customizable.

Here’s your complete roadmap to using WordPress as a headless CMS with modern front-end frameworks.

What’s a Headless WordPress?

Before diving into the “how,” let’s nail down the “what.”

  • Traditional WordPress: CMS + Front-end tightly integrated.
  • Headless WordPress: CMS + Front-end decoupled, communicating via APIs.

Why decouple with Headless WordPress?

  • Flexibility: Create unique UIs without PHP or theme constraints.
  • Performance: Faster load times with modern JS frameworks.
  • Security: Separation reduces attack surface on front-end.
  • Scalability: Independently scale front-end and back-end.

How Headless WordPress Works:

  • Your WordPress backend serves content via REST or GraphQL APIs.
  • A front-end framework (React, Next.js, Gatsby, Vue, Svelte) fetches and renders this content.

Example: Think of WordPress as your restaurant’s kitchen (back-end), preparing content (food). Your front-end framework is the waitstaff, delivering meals smoothly to the customer (user).


Setting Up WordPress as a Headless CMS

Here’s the simplest way to set things up:

  • Install WordPress normally, preferably on a subdomain (e.g., cms.yoursite.com).
  • Configure WordPress to serve content through APIs.
  • Use a front-end framework to fetch and display this data.

Tools You’ll Need:

  • WordPress REST API (built-in)
  • WPGraphQL plugin (optional but recommended)
  • Advanced Custom Fields (ACF) for custom content types (optional)
  • Managed WordPress Host: Easy backend management (e.g., Cloudways, Kinsta, WP Engine)
  • Front-end Hosting: Netlify, Vercel, or AWS Amplify for blazing-fast deployments

Choosing the Best Front-end Framework for WordPress

Pick a front-end framework based on your project’s needs:

1. React / Next.js

  • Best for dynamic sites with frequent content changes.
  • Excellent SEO and performance.
  • Rich ecosystem (plugins, tools).

Quick Example:

  • Fetch posts via GraphQL or REST.
  • Render dynamic components using React hooks.

2. Gatsby

  • Ideal for static, lightning-fast websites.
  • Built-in GraphQL support and fantastic developer experience.

Quick Example:

  • Pull WordPress content at build time.
  • Deploy a super-fast static site to Netlify or Vercel.

3. Vue / Nuxt.js

  • Great for simplicity and a medium learning curve.
  • Powerful server-side rendering capabilities.

Quick Example:

  • Consume WordPress REST API or GraphQL endpoints.
  • Integrate easily with modern UI components.

4. Svelte

  • Lightweight, extremely fast, next-generation front-end.
  • Minimal JS bundle sizes, perfect for performance-focused sites.

Quick Example:

  • Fetch content via REST APIs easily.
  • Minimal setup and blazing-fast UI rendering.

Connecting Your Front-end to WordPress APIs

Using WordPress REST API

The WordPress REST API lets you fetch your WordPress content (posts, pages, comments, etc.) using HTTP requests. Think of it as a universal interface between WordPress (your content management system) and the rest of the world (your apps, front-end websites, or even other services).

WordPress includes a built-in REST API out of the box—no additional plugins are needed.


Accessing the REST API Endpoints

Every WordPress site exposes the REST API at this URL format:

https://yourwebsite.com/wp-json/wp/v2/

For example, if your site is example.com, your API endpoint is:

https://example.com/wp-json/wp/v2/

Try opening this URL in your browser to see the available endpoints. You’ll see JSON data—this means your API is active and accessible.


Fetching Posts via REST API

Let’s fetch a list of posts from your WordPress backend using JavaScript:

Example JavaScript (simple fetch call):

fetch('https://example.com/wp-json/wp/v2/posts')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(posts => {
    console.log(posts);
    // Do something with your posts here!
  })
  .catch(error => {
    console.error('Error fetching posts:', error);
  });

Explanation:

  • The fetch() method makes an HTTP request to the API URL.
  • The response is converted to JSON data.
  • Posts data is logged to the console.

Displaying Posts on Your Front-end

Let’s now render these posts on a basic HTML page:

HTML + JavaScript Example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width">
  <title>My WordPress Posts</title>
</head>
<body>
  <h1>Latest Posts</h1>
  <ul id="posts"></ul>

  <script>
    fetch('https://example.com/wp-json/wp/v2/posts')
      .then(response => response.json())
      .then(posts => {
        const postsList = document.getElementById('posts');

        posts.forEach(post => {
          // Create a list item for each post
          const listItem = document.createElement('li');

          // Add a link to the post
          const postLink = document.createElement('a');
          postLink.href = post.link;
          postLink.innerHTML = post.title.rendered;

          // Append link to list item, and item to list
          listItem.appendChild(postLink);
          postsList.appendChild(listItem);
        });
      })
      .catch(error => {
        console.error('Oops, something went wrong:', error);
      });
  </script>
</body>
</html>

What this does:

  • Fetches post data from your WordPress backend.
  • Loops through posts to dynamically create list items (<li>).
  • Inserts post titles as clickable links.

Fetching Specific Post Details

You might want details of a single post. Here’s how to do it:

API URL format for a single post:

https://example.com/wp-json/wp/v2/posts/{post_id}

Example JavaScript:

fetch('https://example.com/wp-json/wp/v2/posts/123') // replace 123 with your actual post ID
  .then(response => response.json())
  .then(post => {
    console.log('Post Title:', post.title.rendered);
    console.log('Post Content:', post.content.rendered);
  })
  .catch(error => {
    console.error('Error fetching post:', error);
  });

Fetching Additional Data (Categories, Tags)

The REST API can also fetch categories, tags, and more.

Example fetching categories:

fetch('https://example.com/wp-json/wp/v2/categories')
  .then(res => res.json())
  .then(categories => {
    categories.forEach(category => {
      console.log('Category:', category.name);
    });
  });

Fetching posts by category:

fetch('https://example.com/wp-json/wp/v2/posts?categories=5') // replace 5 with category ID
  .then(res => res.json())
  .then(posts => {
    console.log('Posts in Category:', posts);
  });

Handling Common Errors & Issues

You might run into CORS (Cross-Origin Resource Sharing) errors if your frontend is on a different domain. For this you will have to set headers via .htaccess on your server:

Header set Access-Control-Allow-Origin "*"

Note: This allows any domain to access your API—use it wisely!)


Authentication (Optional Advanced Step)

If you need protected content or posts behind authentication:

  • Install JWT Authentication.
  • Follow its simple documentation for generating tokens and securing endpoints.

GraphQL is a modern alternative to REST APIs. Instead of multiple endpoints, GraphQL uses just one single endpoint. You define exactly which data you want through queries, and GraphQL returns precisely that—no extra bloat.

Key benefits:

  • Less data: Get only what you ask for.
  • Single endpoint: One URL for all your queries.
  • Better performance: Less data means faster apps.
  • Simpler frontend code: Easier integration with front-end frameworks.

Installing GraphQL in WordPress

image 3

GraphQL isn’t built into WordPress by default, but setup is easy:


Understanding Basic GraphQL Queries

GraphQL queries look like JSON, and they clearly state what data you want:

Simple Query Example (fetching posts):

query {
  posts {
    nodes {
      id
      title
      date
      excerpt
    }
  }
}
  • This query fetches a list of posts with id, title, date, and excerpt.
  • nodes are items in the response.

Running Your First Query

You can test GraphQL queries easily:

Using the built-in GraphiQL IDE:
  • WPGraphQL plugin includes GraphiQL IDE at: https://example.com/wp-admin/admin.php?page=graphiql-ide
  • Log into your WordPress dashboard.
  • Run queries directly in the browser to test before coding.

Try entering this query into GraphiQL to fetch recent posts:

query {
  posts(first:5) {
    nodes {
      id
      title
      date
    }
  }
}

This fetches the latest 5 posts.


Fetching Data with JavaScript (Using Fetch API)

Here’s how you fetch GraphQL data with plain JavaScript:

Example JavaScript Code:

const query = `
  query {
    posts(first: 3) {
      nodes {
        id
        title
        excerpt
      }
    }
  }
`;

fetch('https://example.com/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query })
})
  .then(res => res.json())
  .then(data => {
    console.log(data.data.posts.nodes);
    // Render your data on the frontend here
  })
  .catch(error => {
    console.error('GraphQL fetch error:', error);
  });

This:

  • Sends your GraphQL query via POST request.
  • Receives data in JSON format.

Rendering GraphQL Data on Front-end

Let’s build a simple HTML page that fetches and renders post titles using JavaScript and GraphQL.

Complete Example (HTML + JavaScript):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width">
  <title>GraphQL + WordPress Posts</title>
</head>
<body>
  <h1>Latest Posts (GraphQL)</h1>
  <ul id="post-list"></ul>

  <script>
    const query = `
      query {
        posts(first: 5) {
          nodes {
            id
            title
            link
          }
        }
      }
    `;

    fetch('https://example.com/graphql', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ query })
    })
      .then(response => response.json())
      .then(result => {
        const posts = result.data.posts.nodes;
        const postList = document.getElementById('post-list');

        posts.forEach(post => {
          const listItem = document.createElement('li');
          const link = document.createElement('a');
          link.href = post.link;
          link.textContent = post.title;
          listItem.appendChild(link);
          postList.appendChild(listItem);
        });
      })
      .catch(error => {
        console.error('Oops, error fetching posts:', error);
      });
  </script>
</body>
</html>

This code:

  • Queries the latest 5 posts.
  • Dynamically renders post titles as clickable links.

Advanced GraphQL Queries (Categories, Tags, Authors)

You can easily fetch categories, tags, authors, and other data:

Example Query (fetch posts with categories and author):

query {
  posts(first: 5) {
    nodes {
      title
      author {
        node {
          name
        }
      }
      categories {
        nodes {
          name
        }
      }
    }
  }
}

In this query:

  • Each post includes its author and related categories.

Working with Custom Fields (ACF)

If you use Advanced Custom Fields, WPGraphQL integrates easily:

Example Query (ACF fields):

query {
  posts {
    nodes {
      title
      acfPostFields {
        customFieldName
        featuredImage {
          sourceUrl
        }
      }
    }
  }
}

Using GraphQL Clients (Example for React)

For React developers, Apollo Client simplifies things:

Example React Code (Apollo Client):

import React from 'react';
import { useQuery, gql } from '@apollo/client';

const GET_POSTS = gql`
  query {
    posts(first: 5) {
      nodes {
        id
        title
      }
    }
  }
`;

const Posts = () => {
  const { loading, error, data } = useQuery(GET_POSTS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.posts.nodes.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

export default Posts;

Enhancing Your Headless Setup: Advanced Tips

Take your project to the next level:

  • Image Optimization: Use plugins like WPGraphQL + WPGraphQL Media for optimized images.
  • Custom Fields: Advanced Custom Fields + WPGraphQL ACF Integration.
  • Authentication: JWT authentication plugins for protected content and user management.

Common Challenges (and How to Fix Them)

Going headless with WordPress offers powerful benefits, but it also introduces a few challenges—especially if it’s your first time. Here is what to watch out for:

1. CORS (Cross-Origin Resource Sharing) Issues

Why it arises: When your frontend (e.g., hosted on Netlify or Vercel) tries to fetch data from WordPress on a different domain, browsers may block requests to prevent security vulnerabilities. This triggers CORS errors.

Common Error:

Access to fetch at 'https://example.com/wp-json/wp/v2/posts' 
from origin 'https://yoursite.com' has been blocked by CORS policy.

How to fix it:

  • Manually configure CORS via .htaccess:
<IfModule mod_headers.c>
  Header set Access-Control-Allow-Origin "https://yoursite.com"
  Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
  Header set Access-Control-Allow-Headers "Content-Type, Authorization"
</IfModule>

2. SEO Issues (Missing Meta & Server-Side Rendering)

Why it arises: Using a purely client-side rendered React or Vue front-end can cause SEO problems because search engines initially see empty pages.

Common Error:

  • Pages indexed without titles or descriptions.
  • Poor performance in search results.

How to fix it:

  • Use Next.js, Gatsby, or Nuxt.js, which offer built-in server-side rendering (SSR) and static site generation (SSG).
  • Implement head management libraries like react-helmet or Next.js’s built-in Head component.

Example (Next.js Head component):

import Head from 'next/head';

const Post = ({ title, description }) => (
  <>
    <Head>
      <title>{title}</title>
      <meta name="description" content={description} />
    </Head>
    <h1>{title}</h1>
  </>
);

3. Slow API Response Times & Latency

Why it arises: Each API call adds network latency. WordPress, if not properly optimized, can slow down front-end load times.

Common Issues:

  • Delayed content loading.
  • Poor user experience on initial load.

How to fix it:

  • Enable server-side or static generation (SSG) to prefetch content at build time.
  • Implement caching with Redis or Memcached on the backend.
  • Use CDNs (Cloudflare, AWS CloudFront) to cache API responses closer to your users.

4. Difficulty Handling Authentication & Secure Content

Why it arises: REST API and GraphQL endpoints may require authentication for private content. Without proper auth, sensitive data becomes inaccessible or vulnerable.

Common Issues:

  • Inability to securely fetch private or restricted content.
  • Difficulty managing user authentication.

How to fix it:

Example JWT authentication request:

fetch('https://example.com/wp-json/jwt-auth/v1/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    username: 'yourusername',
    password: 'yourpassword'
  })
})
.then(res => res.json())
.then(token => {
  console.log('Your token:', token);
});

5. Complex Routing & URL Structure Issues

Why it arises: Front-end frameworks handle routing differently than traditional WordPress. Matching URLs exactly can get tricky, especially if your content structure is complex.

Common Issues:

  • Broken or incorrect permalinks.
  • Difficulty maintaining clean URL structures.

How to fix it:

  • Leverage built-in routing in frameworks like Next.js, Nuxt.js, or Gatsby.
  • For dynamic URLs, use dynamic routing with frameworks.

Example (Next.js Dynamic Routing):

pages/posts/[slug].js
import { useRouter } from 'next/router';

const PostPage = () => {
  const router = useRouter();
  const { slug } = router.query;

  return <p>Post slug: {slug}</p>;
};

6. Difficulty in Managing Content Previews & Editing

Why it arises: Editors might lose visual previews when publishing or editing posts, since traditional WordPress previews rely on integrated themes.

Common Issue:

  • Editors can’t preview changes before publishing.

How to fix it:

  • Use WPGraphQL’s preview feature to enable previewing content in your front-end.
  • Set up custom preview environments (Netlify or Vercel preview deployments).

7. Security Concerns (API Exposure)

Why it arises: Exposing WordPress APIs publicly can increase security risks if not managed properly.

Common Issues:

  • Increased vulnerability to brute force and other attacks.

How to fix it:

  • Implement rate-limiting on your API.
  • Secure API endpoints using authentication.
  • Use security plugins like Wordfence or limit access by IP.

8. Cache Management & Content Updates

Why it arises: Caching can cause stale content on front-ends, especially with static generation setups.

Common Issues:

  • Front-end doesn’t reflect changes immediately after publishing.

How to fix it:

  • Configure incremental static regeneration (ISR) with Next.js.
  • Manually trigger rebuilds using webhooks from WordPress to your front-end host (Netlify, Vercel).

Quick Example (ISR in Next.js):

export async function getStaticProps() {
  const data = await fetch('https://example.com/wp-json/wp/v2/posts');
  const posts = await data.json();

  return {
    props: { posts },
    revalidate: 60, // revalidates every 60 seconds
  };
}

Should You Always Go Headless? (The Truth)

Short answer: Not always. Consider going headless if:

  • You prioritize performance above everything.
  • Your site needs custom interactions or complex animations.
  • You want the latest front-end technology stack.

But if you need quick and simple blogs or standard eCommerce setups, traditional WordPress is still your friend.

Disclaimer: My content is reader-supported, meaning that if you click on some of the links in my posts and make a purchase, I may earn a small commission at no extra cost to you. These affiliate links help me keep the content on gauravtiwari.org free and full of valuable insights. I only recommend products and services that I trust and believe will genuinely benefit you. Your support through these links is greatly appreciated—it helps me continue to create helpful content and resources for you. Thank you! ~ Gaurav Tiwari