>_blog
Mar 14, 2026 // 9 min read

WPNuxt 2.0: Type-Safe Headless WordPress for Nuxt 4

Announcing WPNuxt 2.0 — type-safe composables, multi-layer caching, Gutenberg block rendering, authentication, and an AI-powered MCP server for headless WordPress with Nuxt

WPNuxt 2.0: Type-Safe Headless WordPress for Nuxt 4
Wouter Vernaillen

Wouter Vernaillen

Full Stack Developer

WPNuxt 2.0 brings type-safe composables, multi-layer caching, Gutenberg block rendering, and full authentication to headless WordPress with Nuxt. After 16 alpha releases, 7 betas, and over 700 commits, it's ready for production.

WPNuxt connects WordPress with Nuxt via GraphQL, generating typed composables from your queries so you can fetch and render WordPress content without writing boilerplate. Version 2 adds the features that were missing to make this a complete solution for production headless WordPress sites.

  • Type-safe composables generated from GraphQL queries with full autocomplete
  • Three-layer caching — server (Nitro SWR), client deduplication, and SSR payload
  • Gutenberg block rendering with 10 built-in Vue components
  • Authentication with password, OAuth, and external provider support
  • AI-powered development via MCP server integration
  • Serverless-ready — no jsdom, works on Vercel out of the box
  • SSG support — full static site generation with prerender route fetching
  • GraphQL mutations — generated useMutation*() composables alongside queries

WPNuxt v1 worked as a single module wrapping nuxt-graphql-middleware, but it had gaps: no built-in caching strategy, no authentication, and serverless deployments on Vercel broke due to jsdom in the server bundle. Version 2 fills those gaps. Three packages (@wpnuxt/core, @wpnuxt/blocks, @wpnuxt/auth) ship from a unified monorepo with synchronized versions, shared testing infrastructure, and a CI compatibility matrix covering multiple WordPress and Nuxt versions.

Upgrading from v1? The MCP server includes a wpnuxt_migrate tool that scans your project and guides you through the changes. See breaking changes below.


Composable API

The composable API is cleaner in v2. The configurable prefix is gone — all composables use a consistent use naming convention. The separate useAsync* composables are replaced by a lazy option.

diff
- const { data } = await useWPPosts()
+ const { data } = await usePosts()

- const { data } = await useWPPageByUri({ uri })
+ const { data } = await usePageByUri({ uri })

- const { data } = useAsyncWPPosts()
+ const { data } = usePosts(undefined, { lazy: true })

Every composable is auto-imported and fully typed. The module generates TypeScript declarations from your GraphQL queries and fragments at build time, so you get autocomplete and type checking all the way from WordPress through to your Vue templates.

Each composable now accepts options for retry, timeout, and caching:

app/pages/blog/[...slug].vue
const { data: post } = await usePostByUri({
  uri: route.params.slug
}, {
  retry: 3,
  retryDelay: 1000,
  timeout: 5000,
  clientCache: false
})

Retry uses exponential backoff by default. Timeouts create an AbortController under the hood and clean up properly when the request completes or the component unmounts.

WPNuxt 2 also generates composables for GraphQL mutations. Define a mutation in your extend/queries/ folder and the module generates a useMutation{Name}() composable — fully typed, auto-imported, and ready to use for creating comments, submitting forms, or any custom WordPress mutation. The usePrevNextPost() composable provides previous/next post navigation out of the box.

Extending Queries

WPNuxt provides default GraphQL queries for posts, pages, menus, and settings. You can override any of these or add your own by creating .gql files in the extend/queries/ folder:

extend/queries/CustomPosts.gql
query CustomPosts($limit: Int = 10) {
  posts(first: $limit) {
    nodes {
      ...Post
      customField
    }
  }
}

This generates useCustomPosts() and a lazy variant automatically — fully typed, auto-imported, and cached.


Multi-Layer Caching

One of the biggest improvements in v2 is a structured three-layer caching system that works out of the box:

LayerScopeWhat It Does
Server (Nitro)All usersSWR-based caching of GraphQL responses (~1-5ms vs ~200-500ms uncached)
Client (GraphQL)Per browserDeduplicates identical queries during navigation
PayloadPer requestPrevents refetch during SSR-to-client hydration

Configure it in nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  wpNuxt: {
    cache: {
      enabled: true,
      maxAge: 300,
      swr: true
    }
  }
})

You can also disable caching per-query for content that needs to be fresh, like authenticated or preview content:

ts
const { data } = await useViewer(undefined, {
  clientCache: false
})

Every deployment automatically generates a unique build hash that invalidates the cache, so stale content never survives a redeploy.

The SSG support deserves a special mention. WPNuxt normalizes WordPress URIs with trailing slashes to ensure consistent cache keys between prerendered payloads and client-side navigation. The getCachedData functions are defined at module level (not inside the composable) to maintain stable function references across SSR and hydration, preventing Nuxt's "incompatible options" warnings.


Content Rendering

The WPContent Component

The <WPContent> component handles WordPress content rendering:

app/pages/[...slug].vue
<template>
  <WPContent :node="page" />
</template>

WPContent automatically detects whether @wpnuxt/blocks is installed. If it is, content renders through the BlockRenderer for structured Gutenberg blocks. Otherwise, it falls back to sanitized HTML via the built-in v-sanitize-html directive.

The component also intercepts clicks on internal links and uses navigateTo() for client-side navigation. It respects modifier keys (Ctrl/Cmd+click for new tab), target="_blank", download attributes, and rel="external" — so link behavior stays predictable.

Gutenberg Blocks

The @wpnuxt/blocks package renders WordPress Gutenberg blocks as Vue components. Each block type maps to a dedicated component:

ComponentBlock Type
CoreParagraphcore/paragraph
CoreHeadingcore/heading
CoreImagecore/image
CoreButtoncore/button
CoreButtonscore/buttons
CoreQuotecore/quote
CoreGallerycore/gallery
CoreSpacercore/spacer
CoreDetailscore/details
EditorBlockFallback for unsupported blocks

Install the blocks package alongside core:

bash
pnpm add @wpnuxt/blocks
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@wpnuxt/blocks']
})

The blocks module automatically extends the Post and Page GraphQL fragments to include editorBlocks data. It also detects @nuxt/ui and uses its components where appropriate — for example, CoreButton renders as a UButton when Nuxt UI is available.

Override any block by creating your own component in components/blocks/. A CoreParagraph.vue in your project takes precedence over the built-in version, giving you full control over rendering without ejecting from the system.

Serverless-Ready Sanitization

Version 1 used @radya/nuxt-dompurify, which pulled jsdom (~5.7 MB) into the server bundle. This broke SSR on serverless platforms like Vercel where the bundle size matters. Version 2 replaces this with a built-in v-sanitize-html directive. On the server, HTML passes through as-is (WordPress is a trusted source). On the client, DOMPurify loads lazily using the native browser DOM — no jsdom required.


Authentication

The @wpnuxt/auth package adds WordPress authentication with three supported flows:

MethodFlowWordPress Plugin
PasswordUsername/password via GraphQL mutationHeadless Login for WPGraphQL
External OAuthGoogle, GitHub, etc.Headless Login for WPGraphQL
WordPress OAuthRedirect to WordPress loginminiOrange WP OAuth Server

All three methods store JWT tokens in secure httpOnly cookies with automatic refresh handling.

app/pages/login.vue
const { user, isAuthenticated, login, logout } = useWPAuth()

await login({ username: 'demo', password: 'secret' })

The useWPUser() composable provides role checking:

ts
const { hasRole, isAdmin, isEditor } = useWPUser()

Authentication is SSR-compatible — cookies are included in server-side requests, so authenticated content renders correctly on first load.


Developer Tools

MCP Server

WPNuxt 2 ships with a Model Context Protocol server that integrates with AI assistants like Claude Code. Add it to your project's .mcp.json:

.mcp.json
{
  "mcpServers": {
    "wpnuxt": {
      "type": "sse",
      "url": "https://wpnuxt.com/mcp"
    }
  }
}

The MCP server provides tools across several categories:

WordPress Discovery — Connect to your WordPress site and explore content types, menus, taxonomies, installed plugins, and available Gutenberg blocks.

Content Fetching — Fetch posts, pages, and sample content directly, or run custom GraphQL queries.

Code Generation — Scaffold pages, components, GraphQL queries, and block renderers for your WPNuxt project. The wpnuxt_init tool generates a complete project structure.

Migration — The wpnuxt_migrate tool scans v1 projects, identifies breaking changes, detects anti-patterns, and generates compatibility helpers for upgrading.

Documentation Proxy — The nuxt_docs and nuxt_ui_docs tools proxy official Nuxt and Nuxt UI documentation, so you only need one MCP server for your WPNuxt project.

The wpnuxi CLI

A standalone CLI tool handles project scaffolding and diagnostics:

bash
# Create a new WPNuxt project
npx wpnuxi init my-site

# Display environment info
npx wpnuxi info

# Run health checks
npx wpnuxi doctor

The doctor command checks your environment variables, WordPress URL validity, GraphQL endpoint reachability, introspection support, and project dependencies. It gives you a clear diagnostic when something isn't connecting.

Vercel Auto-Configuration

WPNuxt v2 auto-detects Vercel deployments and applies the right settings:

  • Native SWR for proper ISR (Incremental Static Regeneration) handling
  • SSR forced for all routes (fixes catch-all route classification issues)
  • WordPress uploads proxy (/wp-content/uploads/** forwarded to your WordPress site) — images and media referenced in WordPress content just work without exposing your WordPress domain
  • @nuxt/image IPX configuration with proper WordPress domain aliases

No manual Vercel configuration needed — it just works.

Additional Configuration

For WordPress sites with public introspection disabled, WPNuxt supports a schemaAuthToken that sends a Bearer token during schema download without ever exposing it in the client bundle:

nuxt.config.ts
wpNuxt: {
  schemaAuthToken: process.env.WPNUXT_SCHEMA_AUTH_TOKEN
}

WPNuxt automatically creates server/graphqlMiddleware.serverOptions.ts and app/graphqlMiddleware.clientOptions.ts with sensible defaults for cookie forwarding, Authorization header passthrough, and preview mode support. Existing files are never overwritten, so custom configurations are preserved.

Running nuxt prepare for the first time triggers an interactive setup that prompts for your WordPress URL, creates .env and .env.example files, sets up .mcp.json for AI assistant integration, and creates the extend/queries/ folder for custom GraphQL queries.


Breaking Changes

If you're upgrading from v1, the key changes are:

  • Composable namesuseWP* prefix dropped: useWPPosts()usePosts(), useWPPageByUri()usePageByUri()
  • Lazy loadinguseAsync* replaced by a lazy option: usePosts(undefined, { lazy: true })
  • Cache configenableCache / cacheMaxAge replaced by cache: { enabled, maxAge, swr }
  • Removed composablesuseFeaturedImage() (use post.featuredImage.node directly), useWPUri() (use useRoute().params)
  • Minimum versions — Nuxt 4.0+, Node.js 20+, nuxt-graphql-middleware 5.x

The WPNuxt MCP server includes a wpnuxt_migrate tool that can scan your v1 project and generate a detailed migration plan.


Getting Started

The fastest way to start a new WPNuxt project:

bash
npx wpnuxi init my-wordpress-site

Or manually:

bash
pnpm add @wpnuxt/core
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@wpnuxt/core'],
  wpNuxt: {
    wordpressUrl: 'https://your-wordpress-site.com'
  }
})

To add blocks and authentication:

bash
pnpm add @wpnuxt/blocks @wpnuxt/auth
nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@wpnuxt/core',
    '@wpnuxt/blocks',
    '@wpnuxt/auth'
  ]
})

Run nuxt prepare and WPNuxt handles the rest — downloading the GraphQL schema, generating composables, and setting up type declarations.


What's Next

WPNuxt 2.0 is stable and ready for production. Here's what's on the roadmap:

  • Cursor-based pagination for large content sets
  • Deeper inner block nesting for complex Gutenberg layouts
  • Additional default queries for taxonomies and search
  • End-to-end tutorials for common use cases (blog, SEO, menus)
  • Media handling deep dive with @nuxt/image optimization patterns

Feedback, feature requests, and bug reports are welcome on the GitHub repository.


Resources

Getting Started:

WordPress Requirements:

Related Posts:

share //
Built with Nuxt UI • © 2026 Wouter Vernaillen