Examples
These examples are taken from the WPNuxt playgrounds — working applications that demonstrate common patterns. Each playground is a complete Nuxt app you can run locally.
pnpm run dev (full playground with Nuxt UI), pnpm run dev:blocks (blocks playground), or pnpm run dev:core (minimal) to explore these examples interactively.Blog Listing
Display a grid of WordPress posts with featured images and excerpts.
<script setup lang="ts">
const { data: posts, pending, refresh, clear } = await usePosts()
</script>
<template>
<article v-for="post in posts" :key="post.id">
<img
:src="getRelativeImagePath(post.featuredImage?.node?.sourceUrl ?? '')"
:alt="post.featuredImage?.node?.altText"
/>
<h2>
<NuxtLink :to="post.uri">{{ post.title }}</NuxtLink>
</h2>
<div v-sanitize-html="post.excerpt" />
<time>{{ post.date }}</time>
</article>
</template>
Source: playgrounds/full/app/pages/index.vue
Single Post / Page (Catch-All Route)
Render any WordPress content by URI using a catch-all route. Supports both Gutenberg blocks and raw HTML content:
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useNodeByUri(
{ uri: route.path },
{ watch: [() => route.path] }
)
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<!-- Structured blocks (requires @wpnuxt/blocks) -->
<BlockRenderer
v-if="post.editorBlocks?.length"
:node="post"
/>
<!-- Fallback: raw HTML content -->
<div
v-else-if="post.content"
v-sanitize-html="post.content"
class="prose prose-lg dark:prose-invert max-w-none"
/>
</article>
</template>
The watch: [() => route.path] option ensures the content refetches when navigating between pages.
Source: playgrounds/full/app/pages/[...slug].vue
Gutenberg Block Rendering
The blocks playground demonstrates rendering WordPress Gutenberg blocks as individual Vue components:
<script setup lang="ts">
const route = useRoute()
const { data: node } = await useNodeByUri(
{ uri: route.path },
{ watch: [() => route.path] }
)
</script>
<template>
<article v-if="node">
<h1>{{ node.title }}</h1>
<BlockRenderer
v-if="node.editorBlocks"
:node="node"
/>
<div
v-else-if="node.content"
v-sanitize-html="node.content"
/>
</article>
</template>
<BlockRenderer> maps each Gutenberg block type to a Vue component (e.g., core/paragraph → CoreParagraph, core/image → CoreImage). See Rendering Blocks for details on customizing block components.
Source: playgrounds/blocks/app/pages/[...slug].vue
Authentication Flow
Login Page
A login page supporting multiple authentication methods (password, OAuth, Headless Login providers):
<script setup lang="ts">
const { login, isLoading, error, isAuthenticated } = useWPAuth()
const router = useRouter()
async function onSubmit(event) {
const result = await login({
username: event.data.username,
password: event.data.password,
})
if (result.success) {
router.push('/')
}
}
// Redirect if already authenticated
watch(isAuthenticated, (value) => {
if (value) router.push('/')
}, { immediate: true })
</script>
Source: playgrounds/full/app/pages/login.vue
Profile Page
Display the authenticated user's profile data:
<script setup lang="ts">
const { isAuthenticated, logout } = useWPAuth()
const { user, fetchUser, getDisplayName, getAvatarUrl, isAdmin } = useWPUser()
const router = useRouter()
// Redirect to login if not authenticated
watch(isAuthenticated, (value) => {
if (!value) router.push('/login')
}, { immediate: true })
// Fetch user data when authenticated
onMounted(async () => {
if (isAuthenticated.value && !user.value) {
await fetchUser()
}
})
</script>
<template>
<div v-if="isAuthenticated && user">
<img :src="getAvatarUrl()" :alt="getDisplayName()" />
<h1>{{ getDisplayName() }}</h1>
<p>{{ user.email }}</p>
<span v-if="isAdmin()">Administrator</span>
<button @click="logout()">Sign out</button>
</div>
</template>
Source: playgrounds/full/app/pages/profile.vue
See the Authentication guide for full setup instructions.
Menu-Driven Navigation
Fetch a WordPress menu and render it as navigation:
<script setup lang="ts">
import type { MenuItemFragment } from '#graphql-operations'
const { data } = await useMenu({ name: 'main' })
const menu = computed(() => {
const wordPressPages = data.value?.map((item: MenuItemFragment) => ({
label: item.label,
to: item.uri,
})) ?? []
return [
{ label: 'Home', to: '/' },
...wordPressPages,
]
})
</script>
<template>
<nav>
<NuxtLink
v-for="item in menu"
:key="item.to"
:to="item.to"
>
{{ item.label }}
</NuxtLink>
</nav>
</template>
This fetches the WordPress menu named "main" and combines it with any hardcoded routes. See Menus for more details.
Source: playgrounds/full/app/app.vue
Interactive Query Options
The full playground includes an interactive demo page where you can toggle query options (lazy, server, immediate, clientCache) and see how they affect data fetching:
const { data: posts, pending, refresh, status } = usePosts(
{},
{
lazy: options.value.lazy,
server: options.value.server,
immediate: options.value.immediate,
clientCache: options.value.clientCache,
}
)
This is useful for understanding how each option affects SSR, client-side navigation, and caching behavior.
Source: playgrounds/full/app/pages/query-options.vue
Minimal Setup (No UI Framework)
The core playground demonstrates WPNuxt without any UI framework — just @wpnuxt/core:
export default defineNuxtConfig({
modules: ['@wpnuxt/core'],
wpNuxt: {
wordpressUrl: 'https://your-wordpress.com',
},
})
All WPNuxt composables (usePosts(), useNodeByUri(), etc.) work without @nuxt/ui or @wpnuxt/blocks. You handle the rendering yourself.
Source: playgrounds/core/