Guide

Examples

Real-world patterns from the WPNuxt playgrounds

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.

Run 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/paragraphCoreParagraph, core/imageCoreImage). 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.

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:

nuxt.config.ts
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/

Copyright © 2026