Migrate to RunxBuild today!! Deploy More while Spending Less

Calculate your savings
unxBuild

Headless WordPress with Vue: REST, WPGraphQL, and the Two Things You End Up Hosting

Sean

Platform Writer

Sep 23, 2026
9 min read

Headless WordPress with Vue means using WordPress purely as a content backend: writers use the familiar WordPress admin, and a separate Vue or Nuxt application fetches that content over an API and renders it as its own frontend, instead of using WordPress themes and PHP templates. WordPress exposes content two ways for this: the built-in REST API, which needs no plugin, and WPGraphQL, a plugin that adds a typed GraphQL schema over the same content. The practical work is fetching posts, handling CORS between two different origins, previewing unpublished drafts, and deciding whether the Vue frontend needs server-side rendering for SEO. All of that, plus the fact that this setup is genuinely two applications to host, not one, is covered below.

Headless WordPress with Vue: REST, WPGraphQL, and the Two Things You End Up Hosting

WordPress earns its reputation as a content editor: the admin is familiar, plugins cover almost every editorial workflow, and non-technical writers can use it without training. What it doesn’t have to be anymore is the thing that renders the page a visitor sees. Splitting those two jobs apart, WordPress as the place content is written and stored, a Vue or Nuxt application as the thing that fetches and displays it, is what headless WordPress means in practice, and it’s a well-supported pattern: WordPress’s REST API ships in core, WPGraphQL is a mature plugin with an active community, and there’s even a dedicated Nuxt module built specifically to reduce the boilerplate of wiring the two together. This covers fetching content both ways, the CORS and authentication details that trip people up, SSR for SEO, and the hosting reality of running two applications instead of one.

Table of contents

REST API or WPGraphQL: picking how content comes out

WordPress’s REST API is available on every install with no plugin: /wp-json/wp/v2/posts returns JSON for published posts, and the same pattern covers pages, categories, tags and custom post types out of the box. It’s the lower-friction starting point, nothing to install, nothing to configure beyond making sure the endpoint isn’t blocked, and it’s a perfectly reasonable choice for a Vue frontend that just needs to list and display posts.

WPGraphQL is a plugin that adds a single /graphql endpoint with a typed schema covering the same content plus relationships between it, which matters once a page needs a post, its author, its featured image and related posts in one request instead of four separate REST calls. The tradeoff is a plugin dependency and a slightly steeper learning curve for a team unfamiliar with GraphQL, against fewer round trips and a schema that documents itself.

  • REST: zero setup, works immediately, more requests for related data
  • WPGraphQL: one plugin, one endpoint, fewer requests for nested content
  • Both expose the same underlying WordPress content; this is a transport choice, not a content-modeling one

Fetching posts into a Vue or Nuxt app

In a plain Vue app, fetching from the REST API is an ordinary HTTP call, typically inside a composable so it’s reusable across components.

async function fetchPosts() {
  const res = await fetch('https://cms.example.com/wp-json/wp/v2/posts?_embed');
  if (!res.ok) throw new Error('Failed to load posts');
  return res.json();
}

The _embed query parameter is worth knowing early: without it, a post’s featured image and author come back as links to fetch separately; with it, WordPress embeds that related data directly in the response, trading a slightly larger payload for far fewer requests. In Nuxt specifically, that fetch belongs in useAsyncData or a server route so it runs during server-side rendering rather than only in the browser, which matters for both perceived load time and SEO, covered further down.

const { data: posts } = await useAsyncData('posts', () =>
  $fetch('https://cms.example.com/wp-json/wp/v2/posts?_embed')
);

Pagination works the same way REST pagination usually does: the same endpoint accepts a page and per_page query parameter, and the response headers carry the total post count and total page count, useful for building a simple pager without a second request just to count how many pages exist.

CORS between the WordPress origin and the frontend origin

WordPress and the Vue frontend almost always live on different origins, different subdomains at minimum, often different domains entirely, which means every fetch from the browser is a cross-origin request, and WordPress needs to say explicitly that it’s allowed. Without a CORS header, the browser blocks the response even though the request technically succeeded, which shows up as a fetch that works in a REST client but fails silently in the app.

// functions.php on the WordPress side
add_action('rest_api_init', function () {
    header('Access-Control-Allow-Origin: https://frontend.example.com');
    header('Access-Control-Allow-Methods: GET');
}, 15);

Scope the allowed origin to the actual frontend domain rather than a wildcard, particularly once authenticated requests are involved; a wildcard origin combined with credentialed requests is exactly the combination browsers refuse to allow for good reason.

A hosting or security plugin already active on the WordPress install can strip or override these headers without warning, which is worth checking first if the header above is in place and the error persists; a caching layer in front of WordPress can have the same effect if it caches a response generated before the header was added.

Authenticating for drafts and private content

Published posts need no authentication at all; the REST API serves them openly by default. Previewing a draft, or fetching anything WordPress considers private, requires the request to authenticate as a user with permission to see it, and WordPress’s session-cookie auth, the kind the admin dashboard uses, doesn’t work cleanly for a separate frontend application.

The practical options are Application Passwords, built into WordPress core since 5.6, which generate a username-and-password pair scoped to the REST API for a specific user without touching their main login credentials, or a JWT authentication plugin that issues a token the frontend stores and sends as a bearer token on each request. Application Passwords are the simpler default for a preview workflow with a known, small set of editors; a JWT plugin makes more sense if the frontend needs to authenticate many different users dynamically rather than a fixed editorial team.

curl -u "editor:xxxx xxxx xxxx xxxx" \
  https://cms.example.com/wp-json/wp/v2/posts?status=draft

Either credential should be scoped as narrowly as the workflow allows: an Application Password created for a dedicated preview account with limited capabilities, rather than an administrator’s own login, keeps the blast radius small if the frontend’s environment variables ever leak.

Server-side rendering and SEO

A pure client-side Vue app fetching from WordPress after the page loads has the same SEO problem any client-rendered app has: search engine crawlers that don’t execute JavaScript, or execute it with a budget, see an empty shell before the content arrives. Nuxt’s SSR mode fetches the content on the server before sending HTML to the browser, which is the more dependable path for a content site where organic search matters, which, for most things built on WordPress, it does.

The practical cost is that every server-rendered request now makes a server-to-server call to WordPress at request time, rather than the browser fetching after the page loads, so WordPress’s response time becomes part of the frontend’s response time. Caching the WordPress responses, or generating pages statically at build time for content that doesn’t change per request, keeps that dependency from becoming a bottleneck as traffic grows.

Static generation at build time is worth considering separately from SSR proper for content that changes infrequently: rebuilding the site on a schedule or via a WordPress webhook when content is published avoids the per-request call to WordPress entirely for pages that don’t need to be fresh to the second.

What headless buys over a traditional WordPress theme, and what it costs

The upside is real: the frontend is free to use any component library, any build tooling, any rendering strategy, without being constrained by PHP templates or a theme’s assumptions. Editors keep the WordPress admin they already know. Our post on Headless Architecture covers the broader tradeoff beyond WordPress specifically, when splitting frontend from backend is worth the added complexity and when it isn’t, which is worth reading before committing to this pattern for a site that might not need it. For a related pattern with a transaction layer instead of a CMS, see Headless Ecommerce.

The cost, beyond the CORS and auth work above, is operational: two applications now need deploying, monitoring and keeping in sync, instead of one WordPress install serving pages directly. That’s a genuine tradeoff, not a formality, and it’s the subject of the next section. It’s also worth being honest that not every WordPress site benefits from this: a brochure site with a handful of pages and no unusual frontend requirements is often better served by a well-chosen theme than by taking on two deployments for a result a visitor can’t tell apart.

Two things to host, not one

Headless WordPress with Vue means exactly what it sounds like operationally: a WordPress install that needs its own hosting, database, backups and updates, and a separate Vue or Nuxt frontend that needs its own build and deploy pipeline, both kept running and both able to reach each other over the network. Skipping either half, a fast frontend pointed at a WordPress backend on cheap, unmanaged hosting, or a solid WordPress install serving a frontend nobody bothered to deploy properly, undoes the benefit of splitting them apart in the first place.

On RunxBuild, that’s managed WordPress for the content side, with a file manager and database browser in the dashboard instead of SFTP and phpMyAdmin, and a static or Node deployment for the Vue or Nuxt frontend on the same dashboard, two services, one place to see both sets of logs. Our post on Nuxt Hosting covers the SSG-versus-SSR decision for the frontend half in more detail, which matters more here than in a Nuxt app with no external content API to call.

How this fits the rest of the stack

Headless WordPress with Vue is a genuinely good pattern when the team wants WordPress’s editorial experience and a frontend that isn’t constrained by PHP templates; it just isn’t a smaller project than a traditional WordPress theme, it’s a different shape of the same size, with a network call where a template include used to be.

Hosting both halves in a way that doesn’t fight the pattern matters as much as the API choice does. The RunxBuild hosting calculator prices managed WordPress next to a static or Node frontend service, so the two pieces this setup actually needs show up as two clear line items instead of a guess.

Useful related references:

FAQ

Should I use the WordPress REST API or WPGraphQL for a Vue frontend?

The REST API needs no plugin and works immediately, which makes it the simpler starting point for straightforward post listing and display. WPGraphQL is a plugin that adds a typed GraphQL schema over the same content, worth adopting once a page needs several related pieces of content, a post, its author, its featured image, related posts, in one request instead of several separate REST calls.

How do I preview unpublished WordPress drafts in a headless Vue frontend?

Authenticate the request as a user with permission to see the draft, since the REST API serves published content openly but requires authentication for anything private. WordPress’s built-in Application Passwords, core since 5.6, generate REST-scoped credentials for a specific user without touching their main login, which is the simplest option for a small, known set of editors previewing content.

Why is my Vue app getting a CORS error fetching from WordPress?

WordPress and the frontend almost always run on different origins, so every browser fetch is cross-origin and WordPress must send an explicit Access-Control-Allow-Origin header naming the frontend’s domain. Without it, the browser blocks the response even though the request reached the server successfully. Scope the allowed origin to the actual frontend domain rather than a wildcard, especially for any authenticated request.

Does a headless WordPress plus Vue site need server-side rendering for SEO?

Generally yes, if organic search matters. A purely client-rendered Vue app fetches content after the page loads, which some crawlers see as an empty shell. Nuxt’s SSR mode fetches from WordPress on the server before sending HTML, which is more dependable for search. The tradeoff is that WordPress’s response time then affects the frontend’s response time, which caching or static generation can offset.

Do I need to host WordPress and the Vue frontend separately?

Yes; headless WordPress means two applications: the WordPress install that stores and serves content through its API, and the Vue or Nuxt frontend that fetches and renders it. Both need their own hosting, deployment and monitoring. Skipping proper hosting for either half, especially treating WordPress as an afterthought once the frontend looks finished, undermines the reason for splitting them apart.

#headless wordpress vue#wordpress rest api vue#wpgraphql nuxt#headless cms vue frontend#wordpress preview drafts vue