Skip to content

Vue Integration

This guide covers how to integrate WP Block to HTML with Vue applications for converting WordPress blocks into Vue components.

Installation

To use WP Block to HTML with Vue, install the core package and Vue integration:

bash
# Using npm
npm install wp-block-to-html

# Using yarn
yarn add wp-block-to-html

# Using pnpm
pnpm add wp-block-to-html

Note that vue is a peer dependency and should already be installed in your project.

Basic Usage

Here's how to convert WordPress blocks to Vue components:

vue
<template>
  <div class="wordpress-content">
    <component 
      v-for="(component, index) in components" 
      :key="index" 
      :is="component.component"
      v-bind="component.props"
    />
  </div>
</template>

<script>
import { convertBlocksToVue } from 'wp-block-to-html/vue';

export default {
  name: 'WordPressContent',
  props: {
    blocks: {
      type: Array,
      required: true
    }
  },
  computed: {
    components() {
      // Convert blocks to Vue components
      return convertBlocksToVue(this.blocks, {
        cssFramework: 'tailwind'
      });
    }
  }
}
</script>

Configuration Options

The convertBlocksToVue function accepts the same options as the core convertBlocks function, with additional Vue-specific options:

vue
<script>
import { convertBlocksToVue } from 'wp-block-to-html/vue';

export default {
  name: 'WordPressContent',
  props: {
    blocks: {
      type: Array,
      required: true
    }
  },
  computed: {
    components() {
      return convertBlocksToVue(this.blocks, {
        // Core options
        cssFramework: 'tailwind',
        classMap: this.customClassMap,
        contentHandling: 'raw',
        
        // Vue-specific options
        components: {
          // Custom component mapping
          'core/image': () => import('@/components/CustomImage.vue')
        },
        wrapBlocks: true,
        globalProps: {
          // Props to pass to all components
          className: 'my-custom-class'
        }
      });
    }
  }
}
</script>

Vue-Specific Options

OptionTypeDefaultDescription
componentsObject{}Map of block names to custom Vue components
wrapBlocksBooleanfalseWhether to wrap each block in a div with data attributes
globalPropsObject{}Props to pass to all generated components

Using Custom Components

You can provide custom Vue components for specific blocks:

vue
<!-- CustomImage.vue -->
<template>
  <div class="image-wrapper">
    <img 
      :src="src" 
      :alt="alt" 
      :width="width" 
      :height="height"
      :class="className"
      loading="lazy"
    />
    <div class="image-caption" v-if="alt">{{ alt }}</div>
  </div>
</template>

<script>
export default {
  name: 'CustomImage',
  props: {
    src: String,
    alt: String,
    width: Number,
    height: Number,
    className: String
  }
}
</script>

Then use it in your content component:

vue
<script>
import { convertBlocksToVue } from 'wp-block-to-html/vue';
import CustomImage from '@/components/CustomImage.vue';

export default {
  computed: {
    components() {
      return convertBlocksToVue(this.blocks, {
        components: {
          'core/image': CustomImage
        }
      });
    }
  }
}
</script>

Handling Events

Vue components can include event handlers:

vue
<!-- CustomButton.vue -->
<template>
  <a 
    :href="url" 
    :class="className"
    @click="handleClick"
  >
    {{ text }}
  </a>
</template>

<script>
export default {
  name: 'CustomButton',
  props: {
    url: String,
    text: String,
    className: String
  },
  methods: {
    handleClick(e) {
      console.log('Button clicked:', this.text);
      // Add analytics tracking or other functionality
      this.$emit('button-click', { text: this.text, url: this.url });
    }
  }
}
</script>

Use the component in your main content component:

vue
<template>
  <div class="buttons-section">
    <component 
      v-for="(component, index) in buttonComponents"
      :key="index"
      :is="component.component"
      v-bind="component.props"
      @button-click="trackButtonClick"
    />
  </div>
</template>

<script>
import { convertBlocksToVue } from 'wp-block-to-html/vue';
import CustomButton from '@/components/CustomButton.vue';

export default {
  props: {
    blocks: Array
  },
  computed: {
    buttonComponents() {
      return convertBlocksToVue(this.blocks, {
        components: {
          'core/button': CustomButton
        }
      });
    }
  },
  methods: {
    trackButtonClick(data) {
      // Track button click events
      console.log('Button click tracked:', data);
    }
  }
}
</script>

Server-Side Rendering with Vue

For server-side rendering in frameworks like Nuxt.js:

vue
<!-- pages/_slug.vue -->
<template>
  <article>
    <h1 v-html="post.title.rendered"></h1>
    <div class="post-meta">
      <time :datetime="post.date">{{ formatDate(post.date) }}</time>
    </div>
    <div class="post-content">
      <component 
        v-for="(component, index) in contentComponents" 
        :key="index" 
        :is="component.component"
        v-bind="component.props"
      />
    </div>
  </article>
</template>

<script>
import { convertBlocksToVue } from 'wp-block-to-html/vue';
import { processBlocksForSSR } from 'wp-block-to-html';

export default {
  async asyncData({ params, $axios }) {
    // Fetch post data from WordPress API
    const response = await $axios.$get(
      `https://your-wp-site.com/wp-json/wp/v2/posts?slug=${params.slug}&_embed=wp:blockdata`
    );
    
    if (response.length === 0) {
      return { notFound: true };
    }
    
    const post = response[0];
    let blocks = [];
    
    if (post._embedded && post._embedded['wp:blockdata']) {
      blocks = post._embedded['wp:blockdata'];
      
      // Apply SSR optimizations
      blocks = processBlocksForSSR(blocks, {
        optimizationLevel: 'balanced'
      });
    }
    
    return {
      post,
      blocks
    };
  },
  computed: {
    contentComponents() {
      return convertBlocksToVue(this.blocks, {
        cssFramework: 'tailwind'
      });
    }
  },
  methods: {
    formatDate(date) {
      return new Date(date).toLocaleDateString();
    }
  }
}
</script>

CSS Framework Integration

WP Block to HTML works well with various CSS frameworks in Vue:

Tailwind CSS

vue
<template>
  <div class="prose lg:prose-xl">
    <component 
      v-for="(component, index) in components" 
      :key="index" 
      :is="component.component"
      v-bind="component.props"
    />
  </div>
</template>

<script>
import { convertBlocksToVue } from 'wp-block-to-html/vue';

export default {
  props: {
    blocks: Array
  },
  computed: {
    components() {
      return convertBlocksToVue(this.blocks, {
        cssFramework: 'tailwind'
      });
    }
  }
}
</script>

Bootstrap

vue
<template>
  <div class="container">
    <div class="row">
      <div class="col-md-8 offset-md-2">
        <component 
          v-for="(component, index) in components" 
          :key="index" 
          :is="component.component"
          v-bind="component.props"
        />
      </div>
    </div>
  </div>
</template>

<script>
import { convertBlocksToVue } from 'wp-block-to-html/vue';
import 'bootstrap/dist/css/bootstrap.min.css';

export default {
  props: {
    blocks: Array
  },
  computed: {
    components() {
      return convertBlocksToVue(this.blocks, {
        cssFramework: 'bootstrap'
      });
    }
  }
}
</script>

Custom CSS Framework

vue
<script>
import { convertBlocksToVue } from 'wp-block-to-html/vue';
import { getClassMap } from 'wp-block-to-html';

export default {
  props: {
    blocks: Array
  },
  computed: {
    customClassMap() {
      // Get the base class map for Tailwind
      const tailwindClasses = getClassMap('tailwind');
      
      // Create custom class map
      return {
        ...tailwindClasses,
        paragraph: {
          base: 'my-custom-paragraph mb-4 text-gray-800',
          alignLeft: 'text-left',
          alignCenter: 'text-center',
          alignRight: 'text-right'
        },
        heading: {
          base: 'font-display',
          level1: 'text-4xl mb-6',
          level2: 'text-3xl mb-5',
          level3: 'text-2xl mb-4',
          level4: 'text-xl mb-3',
          level5: 'text-lg mb-2',
          level6: 'text-base mb-2'
        }
      };
    },
    components() {
      return convertBlocksToVue(this.blocks, {
        classMap: this.customClassMap
      });
    }
  }
}
</script>

Composition API Support

WP Block to HTML works with Vue's Composition API:

vue
<template>
  <div class="wordpress-content">
    <component 
      v-for="(component, index) in components" 
      :key="index" 
      :is="component.component"
      v-bind="component.props"
    />
  </div>
</template>

<script setup>
import { computed } from 'vue';
import { convertBlocksToVue } from 'wp-block-to-html/vue';

const props = defineProps({
  blocks: Array
});

const components = computed(() => {
  return convertBlocksToVue(props.blocks, {
    cssFramework: 'tailwind'
  });
});
</script>

TypeScript Support

WP Block to HTML includes TypeScript definitions for Vue integration:

vue
<template>
  <div class="wordpress-content">
    <component 
      v-for="(component, index) in components" 
      :key="index" 
      :is="component.component"
      v-bind="component.props"
    />
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
import { convertBlocksToVue, VueConversionOptions } from 'wp-block-to-html/vue';
import { WordPressBlock } from 'wp-block-to-html';

interface CustomImageProps {
  src: string;
  alt: string;
  width?: number;
  height?: number;
  className?: string;
}

export default defineComponent({
  name: 'WordPressContent',
  props: {
    blocks: {
      type: Array as PropType<WordPressBlock[]>,
      required: true
    }
  },
  setup(props) {
    const options: VueConversionOptions = {
      cssFramework: 'tailwind'
    };
    
    const components = computed(() => {
      return convertBlocksToVue(props.blocks, options);
    });
    
    return {
      components
    };
  }
});
</script>

Performance Optimization

For optimal performance with Vue:

  1. Component Caching: Use v-once for static components
vue
<template>
  <div class="static-content">
    <component 
      v-for="(component, index) in staticComponents" 
      :key="index" 
      :is="component.component"
      v-bind="component.props"
      v-once
    />
  </div>
</template>
  1. Async Components: Use Vue's async component loading for heavy components
vue
<script>
export default {
  computed: {
    components() {
      return convertBlocksToVue(this.blocks, {
        components: {
          'core/gallery': () => import('@/components/Gallery.vue')
        }
      });
    }
  }
}
</script>
  1. Keep Components Simple: Avoid unnecessary reactivity

Vuex Integration for State Management

If you're using Vuex for state management:

vue
<script>
import { mapState } from 'vuex';
import { convertBlocksToVue } from 'wp-block-to-html/vue';

export default {
  computed: {
    ...mapState(['blocks']),
    components() {
      return convertBlocksToVue(this.blocks, {
        cssFramework: 'tailwind'
      });
    }
  }
}
</script>

Vuex store setup:

js
// store/index.js
import { createStore } from 'vuex';

export default createStore({
  state: {
    blocks: [],
    post: null
  },
  mutations: {
    SET_BLOCKS(state, blocks) {
      state.blocks = blocks;
    },
    SET_POST(state, post) {
      state.post = post;
    }
  },
  actions: {
    async fetchPost({ commit }, slug) {
      try {
        const response = await fetch(
          `https://your-wp-site.com/wp-json/wp/v2/posts?slug=${slug}&_embed=wp:blockdata`
        );
        const posts = await response.json();
        
        if (posts.length > 0) {
          const post = posts[0];
          commit('SET_POST', post);
          
          if (post._embedded && post._embedded['wp:blockdata']) {
            commit('SET_BLOCKS', post._embedded['wp:blockdata']);
          }
        }
      } catch (error) {
        console.error('Error fetching post:', error);
      }
    }
  }
});

Complete Example

Here's a complete example of using WP Block to HTML with Vue:

vue
<!-- components/WordPressPost.vue -->
<template>
  <article v-if="post" class="wordpress-post">
    <header>
      <h1 v-html="post.title.rendered"></h1>
      <div class="post-meta">
        <time :datetime="post.date">{{ formatDate(post.date) }}</time>
      </div>
    </header>
    
    <div class="post-content">
      <component 
        v-for="(component, index) in contentComponents" 
        :key="index" 
        :is="component.component"
        v-bind="component.props"
      />
    </div>
  </article>
  <div v-else-if="loading" class="loading">Loading post...</div>
  <div v-else class="not-found">Post not found</div>
</template>

<script>
import { ref, computed, onMounted } from 'vue';
import { convertBlocksToVue } from 'wp-block-to-html/vue';
import { processBlocksForSSR } from 'wp-block-to-html';

export default {
  name: 'WordPressPost',
  props: {
    slug: {
      type: String,
      required: true
    }
  },
  setup(props) {
    const post = ref(null);
    const blocks = ref([]);
    const loading = ref(true);
    const error = ref(null);
    
    const contentComponents = computed(() => {
      return convertBlocksToVue(blocks.value, {
        cssFramework: 'tailwind',
        contentHandling: 'raw'
      });
    });
    
    const formatDate = (date) => {
      return new Date(date).toLocaleDateString();
    };
    
    const fetchPost = async () => {
      try {
        loading.value = true;
        const response = await fetch(
          `https://your-wp-site.com/wp-json/wp/v2/posts?slug=${props.slug}&_embed=wp:blockdata`
        );
        
        if (!response.ok) {
          throw new Error('Failed to fetch post');
        }
        
        const posts = await response.json();
        
        if (posts.length > 0) {
          post.value = posts[0];
          
          if (post.value._embedded && post.value._embedded['wp:blockdata']) {
            // Apply SSR optimizations to blocks
            blocks.value = processBlocksForSSR(
              post.value._embedded['wp:blockdata'],
              { optimizationLevel: 'balanced' }
            );
          }
        }
      } catch (err) {
        error.value = err.message;
      } finally {
        loading.value = false;
      }
    };
    
    onMounted(fetchPost);
    
    return {
      post,
      loading,
      error,
      contentComponents,
      formatDate
    };
  }
}
</script>

<style scoped>
.wordpress-post {
  max-width: 800px;
  margin: 0 auto;
}

.post-meta {
  color: #666;
  margin-bottom: 2rem;
}

.loading, .not-found {
  text-align: center;
  padding: 2rem;
}
</style>

Troubleshooting Vue Integration

Common Issues

  1. Components Not Rendering: Ensure blocks array has the correct format with blockName property
  2. Missing Block Handlers: If custom blocks aren't rendering, make sure they have appropriate handlers
  3. SSR Hydration Issues: For SSR, ensure Vue components produce the same output as the server-rendered HTML

Hydration Solutions

For Vue 3 and Nuxt 3, prevent hydration mismatches:

vue
<template>
  <div>
    <client-only>
      <component 
        v-for="(component, index) in components" 
        :key="index" 
        :is="component.component"
        v-bind="component.props"
      />
    </client-only>
    
    <template #fallback>
      <div v-html="staticHtml"></div>
    </template>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import { convertBlocksToVue } from 'wp-block-to-html/vue';
import { convertBlocks } from 'wp-block-to-html';

const props = defineProps({
  blocks: Array
});

const components = computed(() => {
  return convertBlocksToVue(props.blocks, {
    cssFramework: 'tailwind'
  });
});

const staticHtml = computed(() => {
  return convertBlocks(props.blocks, {
    cssFramework: 'tailwind'
  });
});
</script>

Released under the MIT License.