Activate personalization
Goals
You have configured the instructions that control personalization. Next you need to update the web app to execute those instructions. This involves the following:
- Understand how the configuration from Canvas is used in a web app.
- Update a web application so layout is read from Canvas.
Overview
During the classification process, Uniform determined some basic facts about the visitor. In the previous section you configured the personalization instructions to use the visitor's classification in order to identify the appropriate components and content to show the visitor.
The next step is to activate personalization in your web app. Uniform handles running the personalization instructions, and it determines which components and content are appropriate. But Uniform only gives a description of what your app should do. It doesn't tell you exactly what to do.
For example, Uniform may tell your web app that the "body" component should be displayed with "architecture" content, but it's up to your web app to determine what, exactly, "body component" and "architecture content" mean. This is what you will implement now.
Add npm packages
Open a terminal in the root of the repository.
Enter the following command:
- Next.js
- Nuxt 3
npm install @uniformdev/canvas @uniformdev/canvas-reactAbout this step
This adds references to the packages for React apps that need to use Canvas.
npm install @uniformdev/canvas @uniformdev/canvas-vueAbout this step
This adds references to the packages for Vue apps that need to use Canvas.
Add personalized content
Edit the following file:
[
{
"id": "home",
"url": "/",
"fields": {
"title": "Home",
"description": "As you navigate around, content will change."
}
},
{
"id": "home-for-architecture",
"fields": {
"title": "Home",
"description": "You triggered the architecture signal."
}
},
{
"id": "development",
"url": "/development",
...
About this change
This adds the data that is used when personalization is activated.
Add enhancer
In this example, the business user specifies the content id for the component (either home or home-for-architecture), but the front-end also needs the fields from the data file. An enhancer is the component that reads that additional data and makes it available to the front-end component.
Add the following file:
import { enhance, EnhancerBuilder } from "@uniformdev/canvas";
import content from "../content/content.json";
//
//Uses the parameter value from the composition
//to look up the topic from the data file. If
//the topic is found, the fields are returned.
const dataEnhancer = async ({ component }) => {
const contentId = component?.parameters?.contentId?.value;
if (contentId) {
const topic = content.find((e) => e.id == contentId);
if (topic) {
return { ...topic.fields };
}
}
};
export default function doEnhance(composition) {
const enhancers = new EnhancerBuilder().data("fields", dataEnhancer);
return enhance({
composition,
enhancers,
});
}
Add component resolver
When you configure personalization, Uniform Context determines which component is appropriate. Context provides the front-end with the name of the component as it is defined in Uniform. This means the front-end must map the Uniform component name to React components.
- Next.js
- Nuxt 3
Add the following file:
import Body from "../src/components/Body";
function UnknownComponent(component) {
return <div>[unknown component: {component.type}]</div>;
}
export default function resolveRenderer({ type }) {
if (type == "defaultBody") {
return Body;
}
return UnknownComponent;
}
Add the following file:
import Body from "../components/Body";
import { DefaultNotImplementedComponent } from "@uniformdev/canvas-vue";
export default function resolveRenderer({ type }) {
if (type == "defaultBody") {
return Body;
}
return DefaultNotImplementedComponent;
}
Add layout component for Canvas
- Next.js
- Nuxt 3
Add the following file:
import Head from "next/head";
import { Slot } from "@uniformdev/canvas-react";
import Footer from "./Footer";
export default function LayoutCanvas({ title }) {
return (
<div className="container">
<Head>
<title>{title}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Slot name="body" />
<Footer />
</div>
);
}
Add the following file:
<script lang="ts" setup>
import Footer from "./Footer.vue";
defineProps<{ title: string }>();
</script>
<template>
<div class="container">
<Head>
<Title>{{ title }}</Title>
<link rel="icon" href="/favicon.ico" />
</Head>
<SlotContent name="body" />
<Footer />
</div>
</template>
Add Canvas to the home page
Adding Canvas to the home page involves making changes to the front-end application. Follow the steps that match your front-end technology.
Next.js
- Steps
- Complete
tip
This section guides you through the process of activating canvas by explaining each step. It takes longer to go through, but it will help you understand why each line of code is needed.
Edit the following file:
/pages/index.jsimport Layout from "../src/components/Layout";
import content from "../content/content.json";
async function getComposition(slug) {
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
return { props: { fields: topic.fields } };
}
export default function Home({ fields }) {
return <Layout content={content} fields={fields} />;
}About this step
This adds a function that will hold the logic needed to retrieve the composition from Uniform.
Edit the following file:
/pages/index.jsimport { CanvasClient } from "@uniformdev/canvas";
import Layout from "../src/components/Layout";
import content from "../content/content.json";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
return { props: { fields: topic.fields } };
}
export default function Home({ fields }) {
return <Layout content={content} fields={fields} />;
}About this step
This adds the client object used to make API calls to Uniform.
Edit the following file:
/pages/index.jsimport { CanvasClient } from "@uniformdev/canvas";
import Layout from "../src/components/Layout";
import content from "../content/content.json";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
const { composition } = await client.getCompositionBySlug({
slug,
});
return composition;
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
return { props: { fields: topic.fields } };
}
export default function Home({ fields }) {
return <Layout content={content} fields={fields} />;
}About this step
This retrieves the composition from Uniform and returns it.
Edit the following file:
/pages/index.jsimport { CanvasClient } from "@uniformdev/canvas";
import Layout from "../src/components/Layout";
import content from "../content/content.json";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
const { composition } = await client.getCompositionBySlug({
slug,
});
return composition;
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
const composition = await getComposition(slug);
return { props: { fields: topic.fields } };
}
export default function Home({ fields }) {
return <Layout content={content} fields={fields} />;
}About this step
This adds a call to the function that retrieves the composition from Uniform.
Edit the following file:
/pages/index.jsimport { CanvasClient } from "@uniformdev/canvas";
import Layout from "../src/components/Layout";
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
const { composition } = await client.getCompositionBySlug({
slug,
});
return composition;
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
const composition = await getComposition(slug);
await doEnhance(composition);
return { props: { fields: topic.fields } };
}
export default function Home({ fields }) {
return <Layout content={content} fields={fields} />;
}About this step
This runs the enhancer, which adds data to the composition.
Edit the following file:
/pages/index.jsimport { CanvasClient } from "@uniformdev/canvas";
import Layout from "../src/components/Layout";
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
const { composition } = await client.getCompositionBySlug({
slug,
});
return composition;
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
const composition = await getComposition(slug);
await doEnhance(composition);
//
//Return props for the home page that
//include the composition and content
//required by the page components.
return {
props: {
composition,
fields: topic.fields,
},
};
}
export default function Home({ fields }) {
return <Layout content={content} fields={fields} />;
}About this step
This returns the props.
Edit the following file:
/pages/index.jsimport { CanvasClient } from "@uniformdev/canvas";
import Layout from "../src/components/Layout";
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
const { composition } = await client.getCompositionBySlug({
slug,
});
return composition;
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
const composition = await getComposition(slug);
await doEnhance(composition);
//
//Return props for the home page that
//include the composition and content
//required by the page components.
return {
props: {
composition,
fields: topic.fields,
},
};
}
export default function Home({ composition, fields }) {
return <Layout content={content} fields={fields} />;
}About this step
This makes the composition available to the React component.
Edit the following file:
/pages/index.jsimport { CanvasClient } from "@uniformdev/canvas";
import { Composition } from "@uniformdev/canvas-react";
import Layout from "../src/components/Layout";
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
import resolveRenderer from "../lib/resolveRenderer";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
const { composition } = await client.getCompositionBySlug({
slug,
});
return composition;
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
const composition = await getComposition(slug);
await doEnhance(composition);
//
//Return props for the home page that
//include the composition and content
//required by the page components.
return {
props: {
composition,
fields: topic.fields,
},
};
}
export default function Home({ composition, fields }) {
return (
<Composition data={composition} resolveRenderer={resolveRenderer}>
<Layout content={content} fields={fields} />
</Composition>
);
}About this step
This adds the Uniform component that handles composition tasks in the app.
Edit the following file:
/pages/index.jsimport { CanvasClient } from "@uniformdev/canvas";
import { Composition } from "@uniformdev/canvas-react";
import LayoutCanvas from "../src/components/LayoutCanvas";
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
import resolveRenderer from "../lib/resolveRenderer";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
const { composition } = await client.getCompositionBySlug({
slug,
});
return composition;
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
const composition = await getComposition(slug);
await doEnhance(composition);
//
//Return props for the home page that
//include the composition and content
//required by the page components.
return {
props: {
composition,
fields: topic.fields,
},
};
}
export default function Home({ composition, fields }) {
return (
<Composition data={composition} resolveRenderer={resolveRenderer}>
<LayoutCanvas composition={composition} fields={fields} />
</Composition>
);
}About this step
This replaces the default layout component with one that is Canvas-aware.
tip
This section gets you to activating canvas as quickly as possible. It does not explain each line of code.
Edit the following file:
import { CanvasClient } from "@uniformdev/canvas";
import { Composition } from "@uniformdev/canvas-react";
import LayoutCanvas from "../src/components/LayoutCanvas";
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
import resolveRenderer from "../lib/resolveRenderer";
async function getComposition(slug) {
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
});
const { composition } = await client.getCompositionBySlug({
slug,
});
return composition;
}
export async function getStaticProps() {
const slug = "/";
const topic = content.find((e) => e.url == slug);
const composition = await getComposition(slug);
await doEnhance(composition);
//
//Return props for the home page that
//include the composition and content
//required by the page components.
return {
props: {
composition,
fields: topic.fields,
},
};
}
export default function Home({ composition, fields }) {
return (
<Composition data={composition} resolveRenderer={resolveRenderer}>
<LayoutCanvas composition={composition} fields={fields} />
</Composition>
);
}
About this step
This is the completed page file.
Nuxt 3
- Steps
- Complete
tip
This section guides you through the process of activating canvas by explaining each step. It takes longer to go through, but it will help you understand why each line of code is needed.
Edit the following file:
/pages/index.vue<script lang="ts" setup>
import content from "../content/content.json";
const slug = "/";
const topic = content.find((e) => e.url == slug);
const { $useComposition } = useNuxtApp();
const { data: compositionData } = await $useComposition({ slug });
</script>
<template>
<Layout :content="content" :fields="topic.fields" />
</template>About this step
This uses the Uniform module to retrieve the composition using the specified slug.
Edit the following file:
/pages/index.vue<script lang="ts" setup>
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
const slug = "/";
const topic = content.find((e) => e.url == slug);
const { $useComposition } = useNuxtApp();
const { data: compositionData } = await $useComposition({ slug });
const composition = await doEnhance(compositionData.value.composition);
</script>
<template>
<Layout :content="content" :fields="topic.fields" />
</template>About this step
This applies the enhancer to the composition.
Edit the following file:
/pages/index.vue<script lang="ts" setup>
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
import resolveRenderer from "../lib/resolveRenderer";
const slug = "/";
const topic = content.find((e) => e.url == slug);
const { $useComposition } = useNuxtApp();
const { data: compositionData } = await $useComposition({ slug });
const composition = await doEnhance(compositionData.value.composition);
</script>
<template>
<Composition
v-if="composition"
:data="composition"
:resolve-renderer="resolveRenderer"
>
<Layout :content="content" :fields="topic.fields" />
</Composition>
</template>About this step
In Canvas you added a component to a slot in a composition. The front-end application must determine which front-end component to use to render the Canvas component. The component you add in this step makes this decision.
Edit the following file:
/pages/index.vue<script lang="ts" setup>
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
import resolveRenderer from "../lib/resolveRenderer";
import LayoutCanvas from "../components/LayoutCanvas.vue";
const slug = "/";
const topic = content.find((e) => e.url == slug);
const { $useComposition } = useNuxtApp();
const { data: compositionData } = await $useComposition({ slug });
const composition = await doEnhance(compositionData.value.composition);
</script>
<template>
<Composition
v-if="composition"
:data="composition"
:resolve-renderer="resolveRenderer"
>
<LayoutCanvas :title="topic.fields.title" />
</Composition>
</template>About this step
This adds the layout component you created earlier.
tip
This section gets you to activating canvas as quickly as possible. It does not explain each line of code.
Edit the following file:
<script lang="ts" setup>
import content from "../content/content.json";
import doEnhance from "../lib/enhancer";
import resolveRenderer from "../lib/resolveRenderer";
import LayoutCanvas from "../components/LayoutCanvas.vue";
const slug = "/";
const topic = content.find((e) => e.url == slug);
const { $useComposition } = useNuxtApp();
const { data: compositionData } = await $useComposition({ slug });
const composition = await doEnhance(compositionData.value.composition);
</script>
<template>
<Composition
v-if="composition"
:data="composition"
:resolve-renderer="resolveRenderer"
>
<LayoutCanvas :title="topic.fields.title" />
</Composition>
</template>
About this step
This is the completed page file.