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

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.
- 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:
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:
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:
| Layer | Scope | What It Does |
|---|---|---|
| Server (Nitro) | All users | SWR-based caching of GraphQL responses (~1-5ms vs ~200-500ms uncached) |
| Client (GraphQL) | Per browser | Deduplicates identical queries during navigation |
| Payload | Per request | Prevents refetch during SSR-to-client hydration |
Configure it in 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:
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:
<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:
| Component | Block Type |
|---|---|
CoreParagraph | core/paragraph |
CoreHeading | core/heading |
CoreImage | core/image |
CoreButton | core/button |
CoreButtons | core/buttons |
CoreQuote | core/quote |
CoreGallery | core/gallery |
CoreSpacer | core/spacer |
CoreDetails | core/details |
EditorBlock | Fallback for unsupported blocks |
Install the blocks package alongside core:
pnpm add @wpnuxt/blocks
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:
| Method | Flow | WordPress Plugin |
|---|---|---|
| Password | Username/password via GraphQL mutation | Headless Login for WPGraphQL |
| External OAuth | Google, GitHub, etc. | Headless Login for WPGraphQL |
| WordPress OAuth | Redirect to WordPress login | miniOrange WP OAuth Server |
All three methods store JWT tokens in secure httpOnly cookies with automatic refresh handling.
const { user, isAuthenticated, login, logout } = useWPAuth()
await login({ username: 'demo', password: 'secret' })
The useWPUser() composable provides role checking:
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:
{
"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:
# 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/imageIPX 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:
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 names —
useWP*prefix dropped:useWPPosts()→usePosts(),useWPPageByUri()→usePageByUri() - Lazy loading —
useAsync*replaced by alazyoption:usePosts(undefined, { lazy: true }) - Cache config —
enableCache/cacheMaxAgereplaced bycache: { enabled, maxAge, swr } - Removed composables —
useFeaturedImage()(usepost.featuredImage.nodedirectly),useWPUri()(useuseRoute().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:
npx wpnuxi init my-wordpress-site
Or manually:
pnpm add @wpnuxt/core
export default defineNuxtConfig({
modules: ['@wpnuxt/core'],
wpNuxt: {
wordpressUrl: 'https://your-wordpress-site.com'
}
})
To add blocks and authentication:
pnpm add @wpnuxt/blocks @wpnuxt/auth
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/imageoptimization patterns
Feedback, feature requests, and bug reports are welcome on the GitHub repository.
Resources
Getting Started:
WordPress Requirements:
- WPGraphQL Plugin
- WPGraphQL Content Blocks (for
@wpnuxt/blocks) - Headless Login for WPGraphQL (for
@wpnuxt/auth)
Related Posts: