Basics
Features
Best Practices
Addon Development
Related Docs
Dark Mode

#Vue Components

If you are not a developer, you should skip this section. Vue Components are an extremely powerful tool for adding complex functionality to websites such as a checkout process, a modal image gallery, store item search and filtering, or blog search.

This documentation assumes you are familiar with Vue's syntax and are experienced and comfortable working with Components in MercuryCMS already. If so, let's get started!

#To Vue or Not to Vue

Most of the Components you will make in MercuryCMS will be Static Components. These are Components whose content is determined at Build time and baked into the Page.

As a rule, if you can add the functionality you need as a Static Component, do it as a Static Component. Vue Components require processing on the frontend and can slow the site down and make it harder to maintain if overused.

There will be many times, however, that it is much simpler and easier to use a Vue Component. For example:

  • You need multiple Components on a Page that have complex internal state. Note that some basic internal state can be managed by adding attributes and classes (see the <tab-list> example in the Components documentation)
  • You need extensive DOM reactivity such as customizing a product where manually updating DOM would be confusing and hard to maintain
  • You need two-way data binding for form fields
  • You're building an extended-time user experience like purchasing tickets for a show that requires getting information from the user in multiple steps
  • You need to manage multiple requests and responses with a live API to show content on a Page such as availability

#Vue Component Basics

To make a Vue Component, simply pick "Vue" in the "Type" dropdown in the Component's Markup tab. This will allow you to write your normal Vue <template>, <style> and <script> tags in the Component's "Markup", "Style", and "Script" tags respectively.

Here's an example of a simple Vue Component written in Mercury:

.click-counter
  button(@click="handleButtonClick") Click Me
  .count You've clicked the button {{{ count }}} time{{{ count == 1 ? '' : 's' }}}!
  .message(v-if="count > 10") You sure like clicking this button!
.click-counter
  border 1px solid rgba(0, 0, 0, 0.1)
  padding 16px
  .count
    margin-top 16px
export default {

  data() {
    return {
      count: 0,
    }
  },

  methods: {

    // handle the user clicking the button
    handleButtonClick() {
      this.count++
    },

  },

}

#Static vs Frontend Data

You must use {{{ }}} rather than {{ }} to use values from Vue's state in the Component's markup. This is to avoid conflicting with MercuryCMS' static templating, however, all Vue attribute binding using : is unchanged.

You will find there are a few MercuryCMS-specific functions and features to keep in mind when working with Vue Components that enhance Vue's functionality. You will also need to keep in mind what data is being staticly baked in at Build time, and what data is available on the frontend.

Avoid baking in large chunks of data. If you pass an entry from a Collection into a Vue Component, be sure to staticly access that data within the Component at Build time or include only the specific data you need in the frontend.

#Script Templating

Unlike Static Components, Vue Component scripts are added for each Component instance used on the Page. This means if you have 20 of the same Vue Component on the Page, that Page will have 20 versions of the script on it.

Each of these Components is a "root" Vue instance created using new Vue() on the Page and targeting the Component's element. One advantage to this, is you can access the data and attr objects within the Component's script. This means you can do things like this:

.media-list
  .media-item(v-for="mediaItem in mediaItems")
    img(:src="mediaItem.url")
    .info
      .name {{{ mediaItem.name }}}
      .caption {{{ mediaItem.caption }}}
.media-list
  .media-item
    display flex
    padding 8px
    border 1px solid rgba(0, 0, 0, 0.1)
    & + .media-item
      margin-top 8px
    img
      width 250px
      height auto
      border-radius 4px
    .info
      margin-left 16px
export default {

  data() {
    return {
      mediaItems: {{ data.mediaItems }},
    }
  },

}

Attributes has a "Media IDs" Array Attribute with a default value of [].

data.mediaItems = mediaList.filter(media => attr.mediaIds.includes(media._id)).map(media => {
  return {
    name: media.name,
    caption: media.caption,
    url: media.getImageUrl(250),
  }
})

Then on the Home Page you could write:

.home-page
  media-list(media-ids=["6266302056f8b6186cd7db0d", "6266302556f8b6186cd7db10", "6266302056f8b6186cd7db0e"])

Here's the resulting script.js for the Page. You can see that the script itself was dynamically generated at Build time.

(async()=>{
    const Vue = (await import("https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.min.js")).default;
    new Vue({
        el: document.querySelector("media-list[data-cid-1]"),
        template: "<div class=\"media-list\"><div class=\"media-item\" v-for=\"mediaItem in mediaItems\"><img :src=\"mediaItem.url\"><div class=\"info\"><div class=\"name\">{{ mediaItem.name }}</div><div class=\"caption\">{{ mediaItem.caption }}</div></div></div></div>",
        data() {
            return {
                mediaItems: [{
                    name: "Blue",
                    caption: "This image is blue.",
                    url: "/media/6266302056f8b6186cd7db0d/medium.webp"
                }, {
                    name: "Red",
                    caption: "This image is red.",
                    url: "/media/6266302056f8b6186cd7db0e/medium.webp"
                }, {
                    name: "Yellow",
                    caption: "This image is yellow.",
                    url: "/media/6266302556f8b6186cd7db10/medium.webp"
                }]
            }
        }
    })
}
)();

In this way, you can Template in any values available at Build time into the script. For more information, see Templating.

#Static Subcomponents

You can use Static Components defined in MercuryCMS in your Vue markup, but be aware they are baked into the Vue Component at build time. This means if you want to do something like bind a dynamic prop to a Static Component, this will not have the effect of updating its style or content because that was all determined during the Build, not while running in the browser.

#Vue Subcomponents

If you want to use Vue Subcomponents, you can define each Vue Component in MercuryCMS and simply use them within another Vue Component. MercuryCMS will automatically create the components: {} object for you, so you don't need to define it.

If you want to have dynamic props used by the Subcomponents, define those as you usually would. You can do all of the normal Vue Subcomponent things like dynamic props, emit and react to events, use refs, call child methods, use Vue's slot system, etc.

#Static Slots

Vue already has its own slot system. When you use slot and template within your Vue Components and Subcomponents, it will utilize that system. If you want to have a Page add content at Build time just like with Static Components, you can use Static Slots.

These work exactly like Slots in Static Components, where the content is determined at Build time and baked into the Vue Component's markup. All features are supported such as named and default Slots and default Slot content.

The parent uses <template slot="someSlotName"> to target either type of Slot. To define a Static Slot within a Vue Component, simply use <slot name="someSlotName" static> instead of <slot name="someSlotName">. You can use both Static Slots and Vue's slots within the same Component, but you should of course avoid naming them the same thing.

#Accessing Vue Component Methods

Vue Components are dynamic and will often have many methods defined within them that you may want to access from other Components on the Page. You can access those functions using the __vue__ key on the element provided by Vue.

For example. Consider this Page's markup:

.gallery-page
  .gallery-page-content
    .gallery-page-columns
      each galleryImage in galleryImageList
        .gallery-page-column
          static-media.gallery-image(media-id=galleryImage.image sizes="300px" onclick=`document.querySelector('.static-media-viewer').__vue__.show("${galleryImage.image}")`)
  static-media-viewer(media-ids=galleryImageList.map(galleryImage => galleryImage.image))

In this example, we show a list of images from the "Gallery Image" Collection and when you click them, it runs the show() method on the Vue Component passing in the Media ID of the image that was clicked.

#Build Scripts

Component Build Scripts give you the ability to create assets needed by a Component during the Build. For example, you may want to make a Blog List Component with a search bar. To improve speed, you want to distill the searchable data into a JSON file and fetch it when the Page loads.

For this, you can use the addAsset() function, which lets you define a file path and the file's content (which will automatically be JSON-encoded if it's an object or array).

The path to the file is implicitly created if it doesn't exist.

You would probably write a Build Script like this:

addAsset('component/blog-post-list/blog_post_index.json', {
  categories: categoryList.map(category => {
    return {
      name: category.name,
      slug: category.slug,
    }
  }),
  blogPosts: blogPostList.map(blogPost => {
    return {
      title: blogPost.title,
      slug: blogPost.slug,
      featuredImage: blogPost.featuredImage,
      featuredImageAltText: blogPost.featuredImageRef.altText || blogPost.featuredImageRef.name,
      featuredImageWidth: blogPost.featuredImageRef.sizes[0].width,
      featuredImageHeight: blogPost.featuredImageRef.sizes[0].height,
      publishedAt: blogPost.publishedAt,
      categorySlug: blogPost.categoryRef.slug,
    }
  })
})

#SEO

A Vue Component's Markup is sent within the Page's script.js file. This means none of the Vue Component's Markup will be visible to search engines or any other SEO tools. This includes any Media, text content, and links.

To solve this problem, you can write special SEO Markup for your Vue Components that is only visible to search engines and SEO tools. SEO Markup is placed directly onto the Page, just at it is with regular Components, but there are a few key differences:

  • SEO Markup is automatically given the style display: none so regular users don't see it.
  • The SEO Markup's main element is automatically given a -seo class name. For example, if your Vue Component is called My Component, your main Vue element will be given the class my-component, and its special SEO Markup will be given the class my-component-seo.
  • Any additional classes you add when using the Vue Component on a Page, Layout, or within another Component do not apply to the SEO Markup element.
  • SEO Markup does not support Slots.
  • Templating of Attributes and other data works as usual.
  • The SEO Markup element will be inserted directly before the Vue Component's main element.

Writing SEO Markup is simple and requires no styling as it's only ever seen by SEO tools. You can write a simple list of Media, text content, and links, although it is still important to group these items in a meaningful way.