Rendering Blocks
@wpnuxt/blocks renders WordPress Gutenberg blocks as individual Vue components.
When to Use
| Approach | Use Case |
|---|---|
<WPContent> | Recommended. Renders blocks or HTML, intercepts internal links for client-side navigation |
v-sanitize-html | Simple HTML content, no customization needed |
@wpnuxt/blocks | Custom rendering per block type, lazy-load images, interactive blocks |
<WPContent> Component
<WPContent> is the recommended way to render WordPress content. It automatically chooses the best rendering strategy and intercepts internal links for client-side navigation.
Basic Usage
<script setup lang="ts">
const route = useRoute()
const { data: page } = await usePageByUri({ uri: route.path })
</script>
<template>
<WPContent v-if="page" :node="page" />
</template>
How It Works
- When
@wpnuxt/blocksis installed, renderseditorBlocksviaBlockRenderer - Falls back to
v-sanitize-htmlfor HTML content - Clicks on
<a>tags pointing to your WordPress domain are handled vianavigateTo()instead of full page reloads - Modifier keys (Ctrl/Cmd+click),
target="_blank",download, andrel="external"links are not intercepted
Disabling Link Interception
Link interception is enabled globally by default via the replaceLinks config option. To disable it per-instance:
<WPContent :node="page" :replace-links="false" />
See Configuration for the global replaceLinks option.
Slot Fallback
Use the default slot to provide custom fallback content:
<WPContent :node="page">
<p>No content available.</p>
</WPContent>
Utility Functions
The auto-imported utilities isInternalLink() and toRelativePath() are available for custom link handling:
// Check if a URL points to your WordPress site
isInternalLink('/about', wordpressUrl) // true
isInternalLink('https://external.com', wordpressUrl) // false
// Convert absolute WordPress URLs to relative paths
toRelativePath('https://wordpress.example.com/hello-world/') // '/hello-world/'
Install @wpnuxt/blocks
pnpm add @wpnuxt/blocks
npm install @wpnuxt/blocks
Configure
export default defineNuxtConfig({
modules: ['@wpnuxt/core', '@wpnuxt/blocks'],
wpNuxtBlocks: {
imageDomains: ['your-wordpress-site.com']
}
})
WordPress Requirement
Install WPGraphQL Content Blocks to expose block data.
GraphQL Fragments
The @wpnuxt/blocks module automatically extends the Post and Page fragments from @wpnuxt/core to include editorBlocks. No manual configuration is needed.
Run nuxt prepare after adding the module to regenerate types.
Usage
<script setup lang="ts">
const route = useRoute()
const { data: page } = await usePageByUri({ uri: route.path })
</script>
<template>
<WPContent v-if="page" :node="page" />
</template>
<WPContent> automatically uses BlockRenderer when @wpnuxt/blocks is installed. You can also use BlockRenderer directly if you don't need link interception.Custom Block Components
Override default rendering by creating components in components/blocks/:
<script setup lang="ts">
defineProps<{
block: {
attributes?: {
content?: string
className?: string
}
}
}>()
</script>
<template>
<p
:class="['my-custom-paragraph', block.attributes?.className]"
v-sanitize-html="block.attributes?.content"
/>
</template>
Included Components
CoreParagraphCoreHeadingCoreImageCoreButtonCoreButtonsCoreGalleryCoreQuoteCoreSpacerCoreDetails
Unsupported blocks fall back to EditorBlock which renders the saved HTML.
See Performance for tips on optimizing block rendering, including lazy-loading images and minimizing layout shift.
Directive
Use v-sanitize-html for safe HTML rendering:
<div v-sanitize-html="block.attributes?.content" />
End-to-End Example
This walkthrough shows how to fetch a page, render its blocks, and customize a specific block type.
What the GraphQL Data Looks Like
When @wpnuxt/blocks is installed, editorBlocks is automatically included in page/post queries. The data returned from GraphQL looks like this:
{
"page": {
"title": "About Us",
"editorBlocks": [
{
"name": "core/heading",
"attributes": {
"level": 2,
"content": "Our Story"
}
},
{
"name": "core/paragraph",
"attributes": {
"content": "We started building headless WordPress sites in 2023..."
}
},
{
"name": "core/image",
"attributes": {
"url": "https://your-site.com/wp-content/uploads/team.jpg",
"alt": "Our team",
"caption": "The team behind WPNuxt"
}
},
{
"name": "core/buttons",
"innerBlocks": [
{
"name": "core/button",
"attributes": {
"text": "Contact Us",
"url": "/contact"
}
}
]
}
]
}
}
Fetching and Rendering
Create a catch-all page that fetches any WordPress page by its URI and renders blocks:
<script setup lang="ts">
const route = useRoute()
const { data: page } = await usePageByUri({ uri: route.path })
if (!page.value) {
throw createError({ statusCode: 404, message: 'Page not found' })
}
useHead({ title: page.value.title })
</script>
<template>
<div>
<h1>{{ page.value?.title }}</h1>
<WPContent v-if="page.value" :node="page.value" />
</div>
</template>
<WPContent> renders editorBlocks via BlockRenderer (which maps each block to its Vue component, e.g., core/heading → CoreHeading) and intercepts internal links for client-side navigation.
Customizing a Block Type
Say you want to style core/image blocks with a caption overlay. Create a custom component:
<script setup lang="ts">
defineProps<{
block: {
attributes?: {
url?: string
alt?: string
caption?: string
className?: string
}
}
}>()
</script>
<template>
<figure :class="['relative overflow-hidden rounded-lg', block.attributes?.className]">
<NuxtImg
v-if="block.attributes?.url"
:src="block.attributes.url"
:alt="block.attributes?.alt || ''"
class="w-full"
/>
<figcaption
v-if="block.attributes?.caption"
class="absolute bottom-0 left-0 right-0 bg-black/50 text-white p-3 text-sm"
v-sanitize-html="block.attributes.caption"
/>
</figure>
</template>
Place this file in components/blocks/ and it automatically overrides the default CoreImage component. No registration or configuration needed — Nuxt auto-imports it.
Related Pages
- Custom Queries — Add custom fields to block queries
- Performance — Optimize rendering and image loading
- Images — Image handling strategies for WordPress content