Skip to main content

Canvas tutorial for Nuxt.js

Introduction

This tutorial walks you through the process of adding both Uniform Canvas and Uniform Optimize capabilities into your app. At the end of this tutorial, you will have a Nuxt app with a page rendering compositions from Uniform Canvas, personalizable and A/B testable with Uniform Optimize. Uniform Optimize is an additional capability that works together with Canvas, bringing decoupled tracking, edge-side personalization and A/B testing into your Nuxt app.

info

Learn more about the Optimize capability here and follow and read Uniform Optmize Nuxt Module for an in-depth explanation.

While this tutorial asks you to go through a vanilla Nuxt setup, you can reuse the same steps if you want to integrate Uniform into your existing Nuxt app.

If you'd like to see the sources for the end result, check out this repo:

git clone https://github.com/uniformdev/nuxt-canvas-helloworld.git

Let's get started!

Prerequisites

  1. an account at uniform.app with a Project created.

    If you haven't created an account or project yet, please head over to uniform.app and create one by following the Guided Setup. Make sure to select the Nuxt option in the framework list.

  2. Give both the core concepts behind Canvas and Glossary a read, so you are familiar with the concepts.
  3. Node.js LTS installed on your machine with either npm or yarn.

If you don't see the "Canvas" tab in your project, you will need to add this capability into your project manually. Go to Settings/Integrations, locate the Canvas integration, and add it to your project. Now the "Canvas" tab is expected to appear inside your main project navigation.

Create API key if you did not use Guided Setup

If you did not create the project using the "Guided Setup" option, or didn't pick Nuxt during Guided Setup, you need to create an API key for your Nuxt app to communicate with Uniform APIs. Follow these steps to create one.

  1. Click your team name (e.g. your@email.com's team) in the header to access your team
  2. Click Settings to access team settings
  3. Click API Keys. Create a new API key and give it a recognizable name, such as canvas-hello-world.
  4. Click Add permissions to a new project and choose the project you just activated Canvas on, then grant the following permissions:
    • Uniform Optimize -> Intent Manifest -> Read
    • Uniform Canvas -> Compositions -> Read Draft
    • Uniform Canvas -> Compositions -> Read Published
  5. Click Create API key
  6. Copy the API key and save it for later
  7. Copy the project ID and save it for later

Step 1: Component & Composition Setup

Step 1.1: Add a new Component

In order to have something to render with Canvas, we need to define a component. This is very similar in concept to defining a Vue component's props type: what data does this component need to render, and which systems do we get the data from. To keep this as simple as possible we'll source content from a local text parameter, but keep in mind for later that the real power of Canvas is linking parameters to other data sources.

  1. Switch to the Canvas tab and navigate to the Component Library.

  2. Click (+) to add a new component. Let's name it Hero.

  3. Toggle the Composition Component checkbox, so that we can use this component as a composition later.

  4. On the component editor, click (+) next to Parameters to add a new parameter

  5. Give the parameter a name such as Greeting, choose Text as the type, and give it a public ID that you will remember later such as greeting.

    info

    The public ID will be this parameter's name in the API and your code, so pick a rememberable and readable name.

  6. Select 'Greeting' as the Title Parameter.

  7. Click Save and Close or type ctrl+shift+s to save and close the component.

Here is how your component is expected to be setup: Hero component

Step 1.2: Add a new Composition

Now that we've defined a component type, we need to create an instance of that type - a Composition - that we can render in Nuxt.

  1. Click Compositions in the left navigation, then (+) to add a new composition

  2. Pick the type we just added, and give it a name you will remember later such as Hero.

  3. Enter some text in the greeting parameter (i.e. 'PC load letter? What does that mean?')

  4. Set the slug in the header to /hero (or some other value you like, but remember it for later)

    info

    The slug is actually optional. You can access this composition from API using the composition id that you can extract from the last segment of the Canvas editor url: https://uniform.app/projects/.../dashboards/canvas/edit/6cdaad1e-4d38-4dd7-a5eb-66fe30c963d2

  5. Here is how your new Hero composition would look like: Hero composition

  6. Click Save and publish or type ctrl+shift+s to save it.

success

Now this composition can be retrieved by from Canvas API using slug /hero or composition id and pulled into your Nuxt app. Let's set it up next.

Step 2: Your Nuxt app setup

Step 2.1: Create your new Nuxt app

This step may be skipped if you are adding Uniform to your existing Nuxt app.

In a command line of your choice (canvas-demo can be any name you want here):

npx create-nuxt-app canvas-demo

When prompted, provide the following options for your new nuxt app (some options are more critical than others):

To skip the option selection where possible, hit "enter".

  • Rendering mode: must be Universal (SSR / SSG)
  • Deployment target: recommended Static (Static/Jamstack hosting), but you can also select Server (requiring Node.js hosting).
# any option - your preference
? Project name: your name
# any option - your preference
? Programming language: TypeScript
# any option - your preference
? Package manager: Yarn
# any option - your preference
? UI framework: None
# any option - your preference (to skip, hit "enter")
? Nuxt.js modules: Axios - Promise based HTTP client
# any option - your preference (to skip, hit "enter")
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
# any option - your preference (to skip, hit "enter")
? Testing framework: None
# !!! IMPORTANT: pick the Universal (SSR / SSG) option
? Rendering mode: Universal (SSR / SSG)
# We recommend the Static option, but you can also select Server (requiring Node.js hosting)
? Deployment target: Static (Static/Jamstack hosting)
# any option - your preference
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
# any option - your preference
? Version control system: Git

Step 2.2: Setup the Canvas module for Nuxt

  1. Install the Uniform package dependencies:

    yarn add isomorphic-unfetch @uniformdev/optimize-nuxt @uniformdev/canvas @uniformdev/canvas-nuxt @uniformdev/cli
  2. Install the npm-run-all package that will be needed to run scripts:

    yarn add npm-run-all --dev
  3. Create an .env file in app root and add the following environment variables:

    UNIFORM_API_KEY=<your-key-here>
    UNIFORM_PROJECT_ID=<your-project-id-here>
  4. Next, we need to polyfill fetch. Create a ~/plugins/fetch.server.js file with the following contents:

    ~/plugins/fetch.server.js
    import 'isomorphic-unfetch';
  5. Add a simple implementation of the tracker within ~/lib/uniform-optimize/tracker.js

    ~/lib/uniform-optimize/tracker.js
    import { createDefaultTracker } from '@uniformdev/optimize-tracker-browser';
    import intentManifest from '~/static/intentManifest.json';

    const localTracker = createDefaultTracker({
    intentManifest,
    });

    export default localTracker;
  6. Update nuxt.config.js with the following two blocks: modules and plugins:

    • The modules addition register both the @uniformdev/canvas-nuxt and @uniformdev/optimize-nuxt modules.
    • The plugins addition registers the polyfill fetch plugin.
    nuxt.config.js
    export default {
    modules: [
    [
    '@uniformdev/canvas-nuxt',
    {
    apiKey: process.env.UNIFORM_API_KEY,
    projectId: process.env.UNIFORM_PROJECT_ID,
    },
    ],
    // Important: add the Optimize module below the Canvas module
    '@uniformdev/optimize-nuxt',
    ],
    plugins: ['~/plugins/fetch.server.js'],
    };
    warning

    Add the @uniformdev/optimize-nuxt module after the @uniformdev/canvas-nuxt module.

  7. replace the existing scripts in your package.json with the following ones.

    package.json
    "scripts": {
    "build": "run-s generate:intents build:nuxt",
    "build:nuxt": "nuxt generate",
    "dev": "run-s generate:intents dev:nuxt",
    "dev:nuxt": "nuxt",
    "generate:intents": "uniform optimize manifest download --output ./static/intentManifest.json",
    "start": "nuxt start"
    }
  8. Run npm run dev or yarn dev to start the dev server. Open http://localhost:3000 in your browser, or use the port that Nuxt suggests.

    You are expected to see the Nuxt app rendering normally: Dev server

    And something similar to this in the console - no errors:

    i Waiting for file changes                                                                     18:40:58
    i Memory usage: 208 MB (RSS: 281 MB) 18:40:58
    i Listening on: http://localhost:3000/ 18:40:58
    No issues found. 18:40:58
    [Uniform] [initialize] Received intent manifest 18:40:59
    WARN [Uniform] [reevaluateSignals] Intents list is empty
    important

    If you are getting the 404 error during this command execution, make sure you followed this step in prerequisites.

Step 3: Configuring data fetching from Canvas API

Now that we have our Nuxt app configured, let's add data fetching and rendering of the compositions from Canvas within our Nuxt app. The Canvas client is a simple promise-based API client that can be used anywhere you like, such as during server-side rendering, static site generation, or even client-side rendering. For the purposes of this tutorial we will use static site generation to fetch the Canvas data.

Step 3.1: Rendering a raw composition

  1. If you haven't already, run npm run dev or yarn dev to get your nuxt.js app running locally.

  2. Open your Nuxt app's pages/index.vue file

  3. Replace the <script /> block with the code below, where we add a asyncData function that will fetch the Canvas composition into a prop to your Nuxt route:

    index.vue
    <script>
    export default {
    async asyncData({ $uniformCanvasNuxt }) {
    const { composition } = await $uniformCanvasNuxt.getCompositionBySlug({
    slug: '/hero'
    })
    return { composition }
    }
    }
    </script>
  4. Modify the <template /> of your index.vue file to rerender the raw composition on this page:

    index.vue
    <template>
    <pre>{{ composition }}</pre>
    </template>
  5. Now you should now see the composition JSON rendered into your browser: Raw Composition is rendering in the browser

tip

If you are not seeing changes taking effect after you save, please do full page reload in your browser (F5).

Step 3.2: Rendering the component within a composition

Now that we have the raw data of the composition rendering, let's render an actual Hero Vue component with the data coming from this composition. Uniform Canvas SDK for Vue comes with some Vue components that facilitate this process and enables automatic personalization and A/B testing while handling the mapping of component types to their Vue implementations.

  1. First, let's create a new Hero.vue component under the /components folder:
components/Hero.vue
<template>
<h1>{{ uniformData.parameters.greeting.value }}</h1>
</template>

<script>
export default {
props: {
uniformData: {
type: Object,
required: true
}
}
};
</script>

info

Notice how we use uniformData as our expected prop, this is required by Uniform SDK at the moment. This prop will contain all parameters, including our greeting parameter and its value.

  1. To render our very simple one-component composition, we use the <composition /> component. This is the root of any rendering of Canvas compositions in Vue. Replace the <template /> within index.vue with the following (leave the asyncData as is):
/pages/index.vue
<template>
<composition :composition="composition">
<Hero :uniformData="composition" />
</composition>
</template>

Now you should see the contents of your parameter in a <h1> on the Nuxt app's index page: Rendering Hero data

In this example we are using <composition/>'s render props API to provide us with a simplified object to render our greeting parameter. This makes it easier to get at the root composition's parameters, but this is also completely valid if want to access component data within a composition:

/pages/index.vue
<template>
<composition :composition="composition">
<h1>{{composition.parameters.greeting.value}}</h1>
</composition>
</template>
success

Congratulations, you successfully rendered your first Canvas composition!

Step 3.3 Adding Component Slots

Now that we've learned how Canvas models components, let's talk about Component Slots. Slots are dynamic regions within a Canvas component that can contain other components (Try not to confuse this with Vue Slots as they are different). These enable developers to give marketers control over the layout of pieces of a page or screen; letting them add, remove, reorder, and personalize or test without needing to involve a developer.

info

You need at least one Slot if you need to add more than one instance of a component on a given composition.

Slots can be nested and added to any component, giving the content architect the capability to model complex layouts and give fine-grained control over what can be changed.

Let's add our first slot to our existing Hero component now.

  1. In the Uniform dashboard, go to your Component Library
  2. Open your existing component, click the Slots tab, and click (+) next to Slots - or type alt+shift+s.
  3. Call the slot Inner Heroes (notice the public id will be different) and select the component we created earlier - Hero into the list. Inner hero
  4. Save and close the existing component, or type ctrl+shift+s.

Now we've added a named slot with public id innerHeroes that we can add zero or more Hero components to. Let's add some to our composition.

  1. Click Compositions and open your composition
  2. You'll see the Inner Heroes slot now appearing under your previously placed Hero component: Inner heros
  3. Click (+) in the Inner Heroes slot to add a new Hero there
  4. Click the added Hero to expand it and set parameters.
  5. Add a few Hero components, if you wish. Since we now have recursive inner heros within heros, you can nest as deep as you'd like: Nested heros
  6. When done, save and publish the composition.

Now it's time to register the Inner Heroes slot on the Nuxt app. Rendering the slot is super easy:

  1. Open pages/index.vue
  2. In the index file within the <composition /> component's body, add a <slot-content slotName="innerHeroes" /> below the Hero component:
index.vue
<template>
<composition :composition="composition">
<Hero :uniformData="composition" />
<slot-content slotName="innerHeroes" />
</composition>
</template>

You expected to see heroes nested within the first hero to render at this point: Inner heroes rendered

info

Nuxt.js will auto resolve any components that you add to the components folder and we configured our component in Canvas to be Hero. Pretty cool huh?

Step 3.4 Adding Component Variants

Sometimes it makes sense to have more than one visual rendering of the same data. Canvas allows you to do this with variants, which are named variations of a single component. A common use for variants might be several renditions of a component, or to provide visual A/B testing.

To create a variant, add it to your Hero component in your component library under the Variants tab: Hero variant

Hero variant

Once the variant is created, you can choose the variant when editing a component of that type. Hero variant selector

In order to access the value of the component variant, simply access uniformData.variant. The modified <template /> of our Hero component contains the variant name rendering in h2:

~/components/Hero.vue
<template>
<div>
<h1>{{ uniformData.parameters.greeting.value }}</h1>
<h2>Variant: {{ uniformData.variant ? uniformData.variant : "default" }}</h2>
</div>
</template>

This is what your app is supposed to render if you make this change Hero variant name

Now to implement this for real, we have at least two options of how we can implement implement this variant:

  1. Conditionally render output within your Vue component based on the variant, which is appropriate if the variation is minor i.e.

    ~/components/Hero.vue
    <template>
    <div v-if="uniformData.variant === 'withButton'">
    <h1>{{ uniformData.parameters.greeting.value }}</h1>
    <button href="#">See more</button>
    </div>
    <div v-else>
    <h1>{{ uniformData.parameters.greeting.value }}</h1>
    </div>
    </template>
  2. Conditionally use a different component to render the variant within a combination of the resolveRenderer function and Vue.js dynamic component rendering, which is appropriate if the variation is major or you prefer this as the source of component selection, i.e.:

    ~/components/Hero.vue
      <template>
    <component :is="resolveVariant" :uniform-data="uniformData" />
    </template>

    <script>
    import HeroDefault from "./HeroDefault.vue";
    import HeroWithButton from "./HeroWithButton.vue";
    export default {
    props: {
    uniformData: {
    type: Object
    }
    },
    component: {
    HeroDefault,
    HeroWithButton
    },
    computed: {
    resolveVariant() {
    switch (this.uniformData?.variant) {
    case "withButton":
    return HeroWithButton;
    default:
    return HeroDefault;
    }
    }
    }
    };
    </script>
    ~/components/HeroDefault.vue
    <template>
    <h1>{{ uniformData.parameters.greeting.value }}</h1>
    </template>

    <script>
    export default {
    props: {
    uniformData: {
    type: Object
    }
    }
    };
    </script>

    ~/components/HeroWithButton.vue
    <template>
    <div>
    <h1>{{ uniformData.parameters.greeting.value }}</h1>
    <button href="#">See more</button>
    </div>
    </template>

    <script>
    export default {
    props: {
    uniformData: {
    type: Object
    }
    }
    };
    </script>

tip

Variants are useful top level constructs for creating visual difference for A/B tests, but for more granular control when a component has several knobs to configure, create parameters. Canvas provides parameter types such as checkboxes, numbers, and dropdowns that are perfect to expose configurable rendering options to authors.

Step 4 Adding CMS data to Canvas

We can add data from other content repositories to Canvas components, such as content management systems and commerce systems. This external data is referenced within the Canvas editor and stored as references to the external data, for example content IDs.

Once this data is linked in the Canvas editor, we need to fetch it from that system within our Nuxt app so that we can use it. This is done using enhancers, which post-process the data from the Canvas Composition API after it is received to augment external system data into it.

Before you continue, read about enhancers and how they work.

The process for setting up pre-built enhancers is specific to each system you want to pull data from. Pick a tool you use and try it out!

Headless CMS

Commerce

Integration parameters

You will need to add a new integration to the desired system within your Project's Integration settings. Each integration brings one or many new parameter types for your components. After the integration is added and configured, you are able to add new parameters that allow sourcing content from the system you integrated with.

For example, if you configured a Contentful integration, your will be able to add a new parameter of type Contentful Entry to any of your components and configure its constraints to allow certain content types to be selected:

Contentful parameter

This will enable users to link to any Contentful entry with your component when editing a composition: Contentful parameter

tip

After the updated value is saved, Uniform Canvas persists the id to that entry, not the whole entry (storing a reference). In order to access the actual entry object, the composition needs to be "enhanced" via Uniform enhancers.

Adding enhancers to your Nuxt app

Here are the changes you need to add a Contentful enhancer into this Nuxt app. The steps for other CMSs and Commerce engines are very similar.

  1. Add the following packages to your Nuxt app:

    yarn add @uniformdev/canvas-contentful contentful @contentful/rich-text-html-renderer
  2. Add the following to the <script /> section of your page, notice the contentfulClient creation that requires some environment variables and the call to enhance that takes place now.

pages/index.vue

<script>

import { createContentfulEnhancer, CANVAS_CONTENTFUL_PARAMETER_TYPES, ContentfulClientList } from "@uniformdev/canvas-contentful";
import { enhance, EnhancerBuilder } from "@uniformdev/canvas";
import { createClient } from "contentful";

export default {
async asyncData({ $uniformCanvasNuxt }) {

// fetching the compostion as we did before
const { composition } = await $uniformCanvasNuxt.getCompositionBySlug({
slug: "/hero"
});

// instantiate a standard Contentful client
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
environment: "master",
accessToken: process.env.CONTENTFUL_DELIVERY_API_KEY
});

// instantiate a ContentfulClientList that contains the Contentful client instance
// NOTE: the ContentfulClientList allows you to use Canvas data that references multiple spaces / environments
// by providing a Contentful client for each space / environment.
const clientList = new ContentfulClientList([
{ spaceId: 'space-id', environmentId: 'environment-id', client },
]);

// instantiate a Contentful enhancer
const contentfulEnhancer = createContentfulEnhancer({ client: clientList });

// calling "enhance" to enhance the composition with data
// we can pass a context object that typically stores the context locale or preview=true|false
await enhance({
composition,
enhancers: new EnhancerBuilder().parameterType(
CANVAS_CONTENTFUL_PARAMETER_TYPES,
contentfulEnhancer
),
context: {}
});

return { composition };
}
};
</script>

Adding environment variables

Each CMS / Commerce integration would require different API keys / credentials. You would need to acquire those from that specific system and drop these into your .env file:

CONTENTFUL_SPACE_ID=your-contentful-space-id
CONTENTFUL_DELIVERY_API_KEY=your-contentful-delivery-api-key
success

Congratulations! You've made it to the end of this tutorial.

Summary

Now that you completed this tutorial, let's recap what we were able to achieve:

  1. We started from a vanilla Nuxt app
  2. Added Uniform Canvas SDK incrementally
  3. Converted a static page to the one that can be dynamically controlled without any code changes by your business users.

Now you have a personalizable and A/B testable Nuxt application in just a few steps!

If you'd like to see the sources for the end result app, check out this repo:

git clone https://github.com/uniformdev/nuxt-canvas-helloworld.git

Troubleshooting

If you get the following 404 error during the first npm run dev command below:

$ uniform optimize manifest download --output ./static/intentManifest.json
โš  Error fetching intent manifest https://uniform.app/api/v1/manifest?projectId=asdasdasdasd
โ— 404 Not Found, content No published data for provided API key; it may be invalid, or no pblish has occurred since creating it.
ublish has occurred since creating it.

This means that you must perform manifest publishing by clicking the Publish button from the Personalization tab.