Skip to main content

Personalizing with Vue

Optimize Deprecation

Uniform Optimize has been deprecated and replaced by Context, a more powerful and flexible personalization solution. Optimize is not being discontinued at this time, but it will not receive updates or new features.

We do not recommend starting a new project with Optimize. If you have an existing project that uses Optimize, you can upgrade your project to Context at no cost using our upgrade guide. If you have any issues with this process you can contact our team.

Most personalization forms involve selecting a single content variation to display to a visitor from a list of possible content variations. For example, we might have a promotional placement with a variation for high-value customers and a default variation for everyone else.

The Uniform <personalize /> component can accept a list of possible variations and emit a rendered Vue component based on the type(s) of variation determined to be most relevant to the visitor.

Variation data

Variations are a JavaScript array of objects with content and personalization data. Variation data must be provided to the <personalize /> component via the :variations prop.

Variation data must implement the following interface:

// TypeScript
interface PersonalizableListItem {
// `intentTag` tells us which intents this variation is relevant to, which
// is used when evaluating whether or not a variation meets personalization criteria.
// Variations with no intent tag are considered 'default' and are shown to anyone.
intentTag?: IntentTags | null;
}
info

Variation data can be of any object type, but the intentTag property must be set on at least one variation for the personalization process to occur.

Generating variation data

While Uniform provides some helpers to simplify working with CMS data, you can choose to construct your variation data manually.

For example, if your Uniform-compatible CMS provides a list of post content items, you can Array.map() them to personalizable variations:

// replace `fetchPostsFromCMS` with something that fetches data from your CMS
const allPosts = await fetchPostsFromCMS();

const mappedPosts = allPosts.map((post) => {
return {
...post,
// Assumes your CMS is capable of attaching Uniform Intent tags
// to content items via `unfrmOptIntentTag` property.
intentTag: post.unfrmOptIntentTag,
};
});
note

Learn more about our CMS-specific helpers in the Content Management documentation.

Now that we have a list of posts in a personalizable format, we can pass these via the :variations prop to the <personalize /> component and let the component determine and render the closest matched variation for our site visitors.

<template>
<personalize :variations="mappedPosts" />
</template>
<script>
import { Personalize } from '@uniformdev/optimize-tracker-vue';

export default {
name: 'PersonalizedComponent',
components: {
Personalize,
},
props: ['mappedPosts'],
}
</script>

Rendering Personalized Data

The Uniform <personalize /> component accepts a list of possible content variations for personalization. After the <personalize /> component has determined which content variation(s) meet the personalization criteria; the component is then responsible for rendering one or more Vue components for the selected personalized variation(s).

There are four ways to handle rendering personalized data:

Single Component

If all personalized content variations share the same data structure and Vue component to render them, you can pass that component directly into the <personalize/> component:

/components/PersonalizedComponent.vue
<template>
<div>
<personalize :variations="variations" :component="getRenderingComponent()" />
</div>
</template>

<script>
import { Personalize } from '@uniformdev/optimize-tracker-vue';
import Hero from './Hero';

export default {
name: 'PersonalizedComponent',
components: {
Personalize,
},
props: ['variations'],
methods: {
getRenderingComponent() {
return Hero;
},
},
};
</script>


Local Component Resolver

Suppose personalized variations can be of different types. In that case, you can use a component resolver to tell <personalize /> which rendering component to use depending on the type property of each content variation. A personalization might allow for Hero or CallToAction types, which would result in a different component being used to render them.

Here is an example of a component resolver:

/lib/componentResolver.js
import { Component } from 'vue';
import MainHero from '../components/Hero';
import CallToAction from '../components/CallToAction';

const componentMap = {
hero: MainHero,
cta: CallToAction,
};

export function componentResolver(variantType) {
const component = componentMap[variantType];
if (!component) {
console.warn(`Component for variantType '${variantType}' could not be resolved.`);
}
return component;
}

The component resolver is intended to return a rendering component, which is just a regular Vue component for the content variation data type. In this example, we're working with Contentful, and each component implementation expects a single Contentful entry to be passed as a prop.

At this point, we can construct a <personalize /> component and pass the component resolver (from above) into it:

/components/PersonalizedComponent.vue
<template>
<div>
<personalize :variations="variations" :component="component">
<template #variation="{ personalized, variation, component }">
<div>
<p>Personalized? {{ personalized }}</p>
<pre>Variation: {{ JSON.stringify(variation, null, 2) }}</pre>
</div>
</template>
</personalize>
</div>
</template>

<script>
import { Personalize } from '@uniformdev/optimize-tracker-vue';

export default {
name: 'PersonalizedComponent',
components: {
Personalize,
},
props: ['variations'],
};
</script>

Global Component Resolver

Some architectures may maintain a component resolver at a global level that provides mapping between variation types and rendering components. Could also use the component resolver outside of a personalization context to determine rendering components for specific content types. For example, a Contentful-based site might use a site-wide component resolver to provide a Vue component when rendering a particular content type.

This architecture can be supported by declaring a component resolver via the UniformOptimizePlugin plugin. The component resolver provided to the plugin will be used by the <personalize /> component if no local :component-resolver prop or :component prop is passed to the component.

/lib/componentResolver.js
import MainHero from '../components/Hero';
import CallToAction from '../components/CallToAction';

const componentMap = {
hero: MainHero,
cta: CallToAction,
};

export function componentResolver(variantType) {
const component = componentMap[variantType];
if (!component) {
console.warn(`Component for variantType '${variantType}' could not be resolved.`);
}
return component;
}
/main.js
import { UniformOptimizePlugin } from '@uniformdev/optimize-tracker-vue';
import trackerInstance from '../lib/localTracker';
import { componentResolver } from '../lib/componentResolver';

Vue.use(UniformOptimizePlugin, {
trackerInstance,
componentResolver,
});

new Vue({
// ...app options
});
tip

When using a global component resolver, it is unnecessary to pass :component or :component-resolver props to the <personalize /> component. If you do pass those props, they will override the global component resolver.

Default Scoped Slot

You may want full control over the rendering process for personalized data in certain scenarios, which is possible by using Vue scoped slots.

The Vue <personalize /> component provides a default scoped slot that can be used as follows:

<personalize :variations="variations">
<template v-slot:default="{ personalized, components, variations }">
<!-- render personalized content here -->
</template>
</personalize>

The scoped slot props are:

  • personalized - A boolean value indicating whether or not personalization occurred.
  • components - An array of Vue components that have been resolved via a provided component resolver, either local or global; if no component resolver exists, this value is undefined.
  • variations - A subset of the :variations prop data provided to the <personalize /> component, this is an array of variations that matched the current personalization conditions.

There are two primary options when using the default scoped slot:

Option 1: Render Variations Data (i.e., no component resolver)

If you want full control over the component resolving process and rendering:

/components/PersonalizedComponent.vue
<template>
<div>
<personalize :variations="variations" :component="component">
<template #variations="{ personalized, components }">
<div>
<h1>
{{ personalized ?
'Personalization happened' :
'Personalization did not happen; default variation shown' }}
</h1>
<div>
<template v-for="(component, index) in components">
<component :is="component" v-if="component" :key="index" />
<div v-else :key="index"></div>
</template>
</div>
</div>
</template>
</personalize>
</div>
</template>

<script>
import { Personalize } from '@uniformdev/optimize-tracker-vue';
import Component from './Component';

export default {
name: 'PersonalizedComponent',
components: {
Personalize,
},
props: ['variations'],
computed: {
component() {
return Component;
}
}
};
</script>

Option 2: With a Component Resolver Either Global or Local

If you want to add a header indicating a component has been personalized and add some wrapper markup around components that have been resolved, via global or local component resolver:

/components/PersonalizedComponent.vue
<template>
<div>
<!--
This example assumes you are using a global component resolver
via the `UniformOptimizePlugin`.
-->
<personalize :variations="variations">
<template v-slot:default="{ personalized, components }">
<div>
<h1>
{{ personalized ?
'Personalization happened' :
'Personalization did not happen; default variation shown' }}
</h1>
<div>
<template v-for="(component, index) in components">
<component :is="component" v-if="component" :key="index" />
<div v-else :key="index"></div>
</template>
</div>
</div>
</template>
</personalize>
</div>
</template>

<script>
import { Personalize } from '@uniformdev/optimize-tracker-vue';

export default {
name: 'PersonalizedComponent',
components: {
Personalize,
},
props: ['variations'],
};
</script>

Personalizing Lists

In many cases, personalization is choosing the single most relevant variation from a set of possible variations. But personalization may also result in selecting more than one of the most-relevant variations. For example:

  • Show the three most relevant promotions
  • Order a list of content items by the most relevant to the visitor's intent scores

The <personalize /> component can support this scenario by passing the count prop:

// Choose the most relevant variation (default)
<personalize count="1" />

// Choose the three most relevant variations
<personalize count="3" />

// Choose all the variations, but reorder them based on relevancy
// to the visitor's intent scores.
<template>
<!-- assumes the `variations` prop is an array of items
<personalize :count="variations.length" :variations="variations" />
</template>
<script>
import { Personalize } from '@uniformdev/optimize-tracker-vue';
export default {
components: {
Personalize,
}
props: ['variations'],
}
</script>

One can also combine this with the default scoped slot. In this example, we show rendering a list of posts where the first post has a special rendering treatment as the most relevant post by combining list personalization with the default scoped slot.

<template>
<personalize :variations="mappedPosts" :count="mappedPosts.length">
<template v-slot:default="{ variations }">
<template v-for="(variation, index) in variations">
<featured-post v-if="index === 0" :post="variation" />
<more-stories v-else :post="variation" />
</template>
</template>
</personalize>
</template>

<script>
import { Personalize } from '@uniformdev/optimize-tracker-vue';
import FeaturedPost from '../components/FeaturedPost';
import MoreStories from '../components/MoreStories';

export default {
components: {
Personalize,
FeaturedPost,
MoreStores,
},
props: ['mappedPosts'],
}
</script>