Uniform how-to/Personalization with Uniform and Netlify Edge Functions
18 min read
Personalizing website content with Uniform and Netlify Edge Functions
Before starting this tutorial, learn these two key terms:
- Personalization, which is content adapted to visitors aimed at enhancing their experience and fostering conversions. You can personalize various criteria, e.g.:
- The location
- The date and time
- A previous activity
- The current activity
- The information disclosed by the visitor
- The information inferred from the visitor's behavior
For example, if a visitor clicks a personalized page but soon bounces off, you could enhance that individual’s next visit based on that behavior.
- Edge-side personalization, which enables personalization to run on a content delivery network (CDN), whose servers cache content in a location closest to visitors.
This tutorial shows you how to personalize website content with Uniform and Netlify Edge Functions.The source code, written in Stackblitz, is on GitHub.
Understanding the prerequisites
To take this tutorial, you must have a working knowledge of—
- React and Next.js
- The Uniform CLI
- The Netlify CLI
In addition, you must have a Uniformaccount, which you can sign up for free; a Uniform project; and a Uniform API key. You also need a Netlify account.
Setting up a Uniform project
First, create a project on the Uniform dashboard. Follow the steps below:
- Log in to the Uniform dashboard at https://uniform.app. This page is then displayed:
- Click the red(+) icon to create a project called
Website Content PersonalizationClick to copy
. The project dashboard is then displayed. - Add an API key for your web app to read data and settings from your Uniform project. Start by clicking the Security tab on the Uniform dashboard and then click API Keys.
- Click the red(+)icon to add an API key and name it
Website Content PersonalizationClick to copy
Key. Set the following permissions:- Uniform Canvas > Compositions > Read Draft
- Uniform Canvas > Compositions > Read Published
- Uniform Context > Read Drafts
- Uniform Context > Manifest > Read
- Click Create API Key.
- Copy the values of the API key and project ID and set them aside for use later.
Note: The API key is displayed only once. If you don’t copy it now, you’ll have to create a new one later.
Building a web app
Now build a Next.js project:
- Open a terminal and run this command line to scaffold a Next.js app:
npx create-next-app website-content-personalizationClick to copy
- Go to your project directory and start the development server on localhost:3000 with this command:
cd website-content-personalization && npm run devClick to copy
- Rename the
styles/Home.module.cssClick to copy
file tostyles/pages.cssClick to copy
to make all the styles globally available and streamline the file’s content to adopt only the specified styles.1.container { 2 padding: 0 2rem; 3} 4.main { 5 min-height: 100vh; 6 padding: 4rem 0; 7 flex: 1; 8 display: flex; 9 flex-direction: column; 10 justify-content: center; 11 align-items: center; 12} 13.title { 14 margin: 0; 15 line-height: 1.15; 16 font-size: 4rem; 17} 18.title, 19.description { 20 text-align: center; 21} 22.description { 23 margin: 4rem 0; 24 line-height: 1.5; 25 font-size: 1.5rem; 26}
1@media (max-width: 600px) { 2 .grid { 3 width: 100%; 4 flex-direction: column; 5 } 6} 7@media (prefers-color-scheme: dark) { 8 .card, 9 .footer { 10 border-color: #222; 11 } 12 .code { 13 background: #111; 14 } 15 .logo img { 16 filter: invert(1); 17 } 18}
- Import the styles into the `pages/_app.js` file with this code:
1import '../styles/globals.css' 2 import '../styles/pages.css'
1 function MyApp({ Component, pageProps }) { 2 return <Component {...pageProps} /> 3 } 4 export default MyApp
- Remove the import of
Home.module.cssClick to copy
frompages/index.jsClick to copy
. Also, create a.envClick to copy
file in your project’s root directory for your environmental variables. Store in.envClick to copy
the value of Uniform’s API key and project ID, which you copied in step 6 of the previous section.1UNIFORM_API_KEY=[!!!API KEY!!!] 2 UNIFORM_PROJECT_ID=[!!!PROJECT ID!!!]
- Create an
srcClick to copy
folder in the project’s root directory and then acomponentsClick to copy
folder undersrcClick to copy
. - Create a
Body.jsxClick to copy
component with the code below:1export default function Body(){ 2 return( 3 <div> 4 <h1 className="title">Website Content</h1> 5 <p className="description"> 6 This is a website content personalization tutorial 7 </p> 8 </div> 9 ) 10}
Activating classification
Classification, an essential feature of personalization, considers the known facts about a website visitor at a point in time and then determines what content to serve. For example, if a visitor is from Germany, you might classify that individual as a resident there and, accordingly, recommend products that are available in the German market.
Uniform offers components that handle classification, which you’ll add to your app. Follow the steps below to activate classification.
- Open a terminal window in your project’s root directory and run the following command to install the packages for React apps that use Uniform:
npm install @uniformdev/context @uniformdev/context-reactClick to copy
- Install the Uniform CLI, the command-line interface through which you can interact with Uniform:
npm install -D @uniformdev/cliClick to copy
- Edit the
package.json Click to copy
file and modify thescriptsClick to copy
object, as follows:This step adds a script to the Next.js1"scripts": { 2 "dev": "npm run download:manifest && next dev", 3 "build": "npm run download:manifest && next build", 4 "download:manifest": "uniform context manifest download --output ./contextManifest.json", 5 "start": "next start" 6 },
devClick to copy
andbuildClick to copy
processes to download from Uniform thecontextClick to copy
manifest, which contains the instructions on how to classify visitors. - Run this command to download the manifest and save it in the
contextManifest.jsonClick to copy
file:npm run download:manifestClick to copy
- Edit the
pages/_app.jsClick to copy
file to read like this:1import { UniformContext } from "@uniformdev/context-react"; 2import { Context, enableContextDevTools } from "@uniformdev/context"; 3import manifest from "../contextManifest.json"; 4import '../styles/globals.css' 5import '../styles/pages.css' 6const context = new Context({ 7 manifest, 8 defaultConsent: true, 9 plugins: [ 10 enableContextDevTools(), 11 ], 12}); 13function MyApp({ Component, pageProps }) { 14 return ( 15 <UniformContext context={context}> 16 <Component {...pageProps} /> 17 </UniformContext> 18 ) 19} 20export default MyApp
The highlighted code above does the following:
- Imports the custom React
contextClick to copy
object. - Imports
ContextClick to copy
andenableContextDevToolsClick to copy
, through which the browser’s developer tools can access the state details from Uniform’scontextClick to copy
object. - Imports the manifest from the
contextManifest.jsonClick to copy
file you saved in a previous step. - Adds and assigns the manifest to the Uniform
contextClick to copy
, sets the default consent for classification on your web app totrueClick to copy
, and sets theenableContextDevToolsClick to copy
plugin. - Leverages the custom
ReactClick to copy
object for rendering the component.
Defining the web app’s layout on Uniform
You configure personalization in Uniform with a tool called Uniform Canvas through the components displayed in your web app’s frontend.
First, create your applayout with Uniform Canvas, as follows:
- In your Uniform project, click Canvas and then Component Library.
- Click the red (+) icon to create a component library and type
BodyClick to copy
under Component Name, which then prefills the Public ID field withbodyClick to copy
. Under Component Icon, selectMenu BoxedClick to copy
. - Click the red (+) icon under the Parameters tab to create a parameter for the component with the following properties and then click OK:
- Parameter Name:
Content IDClick to copy
- Public ID:
contentIdClick to copy
- Type:
TextClick to copy
- Required:
TrueClick to copy
- Click Save and close.
Next, create a
PageClick to copy
component, a composition component that embeds the BodyClick to copy
component. Follow the steps below.- Click the red(+)icon as displayed in the above image.
- Enter the following
- Component Name:
PageClick to copy
- Public ID:
pageClick to copy
- Component Icon:
file-documentClick to copy
- Composition Component:
TrueClick to copy
- Click the Slots tab. A slot is a spot with which you personalize a webpage. Based on the slot’s configuration, Uniform determines which component to place in the slot and what facts are known about the visitor as a result of that individual’s activities on the webpage.
- Click the red (+) button and enter the following values in the form that is displayed:
- Slot Name:
bodySlotClick to copy
- Public ID:
bodySlotClick to copy
- Minimum:
1Click to copy
- Maximum:
1Click to copy
- Allowed Components: Select Specify allowed components, followed by Body and Personalization.
5. Click OK and then Save and close.
Now create a composition that represents your index page:
- Click Compositions and then the red (+) button to create a composition.
- In the Add a composition form that is displayed, select the Page component under Select a composition type and type
Index PageClick to copy
under Name. - Click the Create button and type
/Click to copy
in the Slug field. - Click the green(+) button and select the Body component.
- Type
indexClick to copy
under Content ID and click Save.
Publishing the web app on Netlify
You’ve now created a simple web app with a webpage that displays text. Deploy the app to Netlify by doing the following:
- Login to your Netlify account. This page is then displayed:
- Import the source from GitHub by first clicking Import from Git.
- Select the repository and branch to be deployed. In your case, you have only the
mainClick to copy
branch, and Netlify is smart enough to detect the project. - Click Deploy site to start the build and deploy process. When the process is complete, your site will be up and running.
Integrating the Uniform project with Netlify
Now that your web app is running on Netlify, which supports edge-side personalization, connect your Uniform project to Netlify to harness the features. Perform the integration as follows:
Create a Netlify access token.
- Navigate to Netlify’s personal access tokens folder.
- Click New Access Token, type a description for the token, and then click Generate token.
- Copy the token and paste it somewhere. You won’t be able access it after clicking away.
Integrate with Uniform
- Click the Integrations tab in the Uniform project.
- In the Integration search field, type
NetlifyClick to copy
, press Enter, and then click the Netlify option. - Click Add to project, paste the access token that you copied in step 3 of the previous subsection, and click Continue. Select your Netlify account under Select Account and, under Select Site, the site that you deployed earlier.Note: You can use the web-hook feature by selecting a build hook, but that’s outside the scope of this tutorial.
- Click Save.
Now that you’ve integrated Netlify with your Uniform project, configure personalization based on Netlify’sgeodata available at the edge. Do so through quirks, which are key-value pairs offered programmatically—in your case, through Netlify Edge.
To view the Netlify quirks, click the Personalization tab in your Uniform project and then Quirks.
Configuring personalization
Now configure personalization in your Uniform project based on a visitor’s location. The steps below personalize for two locations, Germany and Poland.
Add signals
As its name implies, a signal is an occurrence you want to track and respond to. For example, a signal could indicate that a visitor is in a particular location, based on which Uniform then displays content specific to that location.
To create two signals, one for Germany and the other for Poland, do the following:
- Click the Personalization tab and then Signals.
- Click the red (+) icon to add a signal.
- Click Quirks on the left navigation and type two signal names: Is
In PolandClick to copy
and IsIn GermanyClick to copy
. - Select Country Code under Quirk Name and equals under Comparison. Under Match, type
PLClick to copy
for Poland andDEClick to copy
for Germany. - Click Save and Close.
- Click Publish to make the signals available.
Personalize a component
Next, add a personalization component to the slot you created in your composition component:
- Navigate to Canvas > Compositions.
- Click the
Index PageClick to copy
composition. - Click the
BodyClick to copy
component and then click Personalize this. - Click the Personalization component and type
Website-content-personalizationClick to copy
under Analytics tracking name. Set the number of variations to 1. - Click the green(+) icon between the
PersonalizationClick to copy
andBodyClick to copy
components and then select theBodyClick to copy
component. Repeat to add anotherBodyClick to copy
component. - Under Content IDs for the two new components, type
isInPolandClick to copy
andisInGermanyClick to copy
, as appropriate. - Select the
BodyClick to copy
component with theisInPolandClick to copy
content ID and clickAdd CriteriaClick to copy
. Add Is in Poland under Personalize This > Set Signal and set the dimension to 50. - Choose Save and Publish from the drop-down menu of the Save button.
Activate personalization in your web app
Now update your web app with the personalization parameters you configured in the previous section. Uniform runs the related instructions and determines which components and what content are appropriate.
- Run this command in the project directory to add references to the packages for the React apps that use Uniform Canvas:
npm install @uniformdev/canvas @uniformdev/canvas-reactClick to copy
- Create a folder called
contentClick to copy
in your project directory and then, in that folder, create a file calledcontent.jsonClick to copy
with this code:Note: Verify that the1[ 2 { 3 "id": "index", 4 "url": "/", 5 "fields": { 6 "title": "Home", 7 "description": "This is the home page" 8 } 9 }, 10 { 11 "id": "isInPoland", 12 "fields": { 13 "title": "Poland", 14 "description": "I am in Poland" 15 } 16 }, 17 { 18 "id": "isInGermany", 19 "fields": { 20 "title": "Germany", 21 "description": "I am in Germany" 22 } 23 } 24
idClick to copy
property matches the value ofcontent IdClick to copy
of the Uniform components. - Create a
libClick to copy
folder in the project’s root directory and, underlibClick to copy
, create a file calledenhancer.jsClick to copy
with the code below:import { enhance, EnhancerBuilder } from "@uniformdev/canvas";Click to copy
import content from "../content/content.json";Click to copy
1const dataEnhancer = async ({ component }) => { 2 const contentId = component?.parameters?.contentId?.value; 3 if (contentId) { 4 const topic = content.find((e) => e.id == contentId); 5 if (topic) { 6 return { ...topic.fields }; 7 } 8 } 9};
1export default async function doEnhance(composition) { 2 const enhancedComposition = { ...composition }; 3 const enhancers = new EnhancerBuilder().data("fields", dataEnhancer); 4 await enhance({ 5 composition: enhancedComposition, 6 enhancers, 7 });
The code above does the following:1 return enhancedComposition; 2}
- Imports the
enhanceClick to copy
method and theEnhancerBuilderClick to copy
object from Uniform Canvas. - Imports the content in the
content.jsonClick to copy
file. - Checks the value of
content IdClick to copy
in Uniform Canvas against the value ofidClick to copy
in thecontent.jsonClick to copy
file and returns the fields property. - Enhances the data and makes it available to the front end.
- Create a file called
lib/resolveRenderer.jsClick to copy
with the code below:import Body from "../src/components/Body";Click to copy
1function UnknownComponent(component) { 2 return <div>[unknown component: {component.type}]</div>; 3}
The code above maps the component1export default function resolveRenderer({ type }) { 2 if (type == "body") { 3 return Body; 4 } 5 return UnknownComponent; 6}
BodyClick to copy
with the public ID ofbodyClick to copy
in your Uniform project and your web app’s componentBodyClick to copy
. - Edit the
src/components/Body.jsxClick to copy
file to read like this:import React, { useEffect, useState } from 'react'Click to copy
1export default function Body(props){ 2 const { fields } = props; 3 const [title, setTitle] = useState(); 4 const [description, setDescription] = useState();
1 useEffect(() => { 2 setTitle(fields.title); 3 setDescription(fields.description); 4 }, []);
The code above displays the1 return( 2 <div> 3 <h1 className="title">{title}</h1> 4 <p className="description">{description}</p> 5 </div> 6 ) 7}
titleClick to copy
anddescriptionClick to copy
properties from thecontent.jsonClick to copy
file. - Create a file called
LayoutCanvas.jsxClick to copy
undersrc/componentsClick to copy
with the code below:1import Head from "next/head"; 2import { Slot } from "@uniformdev/canvas-react";
The code above adds a layout for Uniform Canvas in your Uniform project. Be sure to specify the correct slot name in the1export default function LayoutCanvas({ title }) { 2 return ( 3 <div className="container"> 4 <Head> 5 <title>{title}</title> 6 <link rel="icon" href="/favicon.ico" /> 7 </Head> 8 <Slot name="bodySlot" /> 9 </div> 10 ); 11
SlotClick to copy
component. - Edit the
pages/index.jsClick to copy
file to read like this:1import { CanvasClient } from "@uniformdev/canvas"; 2import { Composition } from "@uniformdev/canvas-react";
import LayoutCanvas from "../src/components/LayoutCanvas";Click to copy
1import content from "../content/content.json"; 2import doEnhance from "../lib/enhancer"; 3import resolveRenderer from "../lib/resolveRenderer";
1async function getComposition(slug) { 2 const client = new CanvasClient({ 3 apiKey: process.env.UNIFORM_API_KEY, 4 projectId: process.env.UNIFORM_PROJECT_ID, 5 }); 6 const { composition } = await client.getCompositionBySlug({ 7 slug, 8 }); 9 return composition; 10}
1export async function getStaticProps() { 2 const slug = "/"; 3 const topic = content.find((e) => e.url == slug);
const composition = await getComposition(slug);Click to copy
1 await doEnhance(composition); 2 return { 3 props: { 4 composition, 5 fields: topic.fields, 6 }, 7 }; 8}
The code above does the following:1export default function Home({ composition, fields }) { 2 return ( 3 <Composition data={composition} resolveRenderer={resolveRenderer}> 4 <LayoutCanvas composition={composition} fields={fields} /> 5 </Composition> 6 ); 7}
- Retrieves the composition from Uniform with the API key and project ID and then returns the composition.
- Finds the
content.jsonClick to copy
file’s url value that matches the slugfor the topic. - Retrieves and enhances the composition before returning a
propsClick to copy
object of the composition and its fields. - Adds to your app the Uniform
CompositionClick to copy
component, which handles composition tasks and makes use of the Uniform Canvas-awareLayoutCanvasClick to copy
component.
Configuring and enabling edge-side personalization on Netlify
- Activate edge-side personalization in your app by running the command below to add the package:
npm i @uniformdev/context-nextClick to copy
- Edit the
pages/_app.jsClick to copy
file to make it read like this:The code above shows the new additions for activating edge-side personalization in your app.1import { UniformContext } from "@uniformdev/context-react"; 2import { Context, enableContextDevTools } from "@uniformdev/context"; 3import manifest from "../contextManifest.json"; 4import { NextCookieTransitionDataStore } from "@uniformdev/context-next"; 5import '../styles/globals.css' 6import '../styles/pages.css' 7const context = new Context({ 8 manifest, 9 defaultConsent: true, 10 transitionStore: new NextCookieTransitionDataStore({}), 11 plugins: [ 12 enableContextDevTools(), 13 ], 14}); 15function MyApp({ Component, pageProps }) { 16 return ( 17 <UniformContext context={context} outputType="edge"> 18 <Component {...pageProps} /> 19 </UniformContext> 20 ) 21} 22export default MyApp
Next, connect your app to Netlify with the Netlify CLI:
- Go to your project directory on a terminal and run this command to authenticate against Netlify:
netlify loginClick to copy
- Type the command below to link your project to your Netlify site name, which you can obtain by clicking Site Overview > Site Details:
1netlify link --name [!!! YOUR NETLIFY SITE NAME !!!
- Add the environment variable
NPM_TOKEN Click to copy
to your machine. Note: You can obtain the value of this environment variable only by contacting Uniform. Do not saveNPM_TOKENClick to copy
in yourenvClick to copy
file because that file only defines the variables within your app whereasNPM_TOKENClick to copy
is for npm to obtain private packages. - Create a file called .
npmrcClick to copy
in your project’s root directory with the code below, which specifies theNPM_TOKENClick to copy
value you obtained from Uniform.1//registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 engine-strict = true
- Add two packages,
context-edge-netlifyClick to copy
andcpxClick to copy
, with this command:1 npm i @uniformdev/context-edge-netlify 2 npm i -D cpx
@uniformdev/context-edge-netlifyClick to copy
contains components for executing personalization instructions in Netlify Edge Functions.cpxClick to copy
copies files across environments (Windows and non-Windows). - Add this command to the
package.jsonClick to copy
file:The script above copies the file generated during the build process to the destination where it will be picked up at buildtime.1{ 2 ... 3 "scripts": { 4 "copy:deno": "cpx node_modules/@uniformdev/context-edge-netlify/dist/index.deno.js lib/uniform", 5 ...
- Run this command:
npm run copy:denoClick to copy
- Add a file called
/netlify/edge-functions/context-middleware.jsClick to copy
with this code:1import { 2 createEdgeContext, 3 createUniformEdgeHandler, 4 buildNetlifyQuirks 5} from "../../lib/uniform/index.deno.js"; 6import manifest from "../../contextManifest.json" assert { type: "json" };
const IGNORED_PATHS = /\/.*\.(ico|png|jpg|jpeg|svg|css|js|json)(?:\?.*|$)$/g;Click to copy
1export default async (request, netlifyContext) => { 2 if ( 3 request.method.toUpperCase() !== "GET" || 4 request.url.match(IGNORED_PATHS) 5 ) { 6 return await netlifyContext.next({ sendConditionalRequest: true }); 7 }
1 const context = createEdgeContext({ 2 manifest, 3 request, 4 });
const originResponse = await netlifyContext.next();Click to copy
const handler = createUniformEdgeHandler();Click to copy
1 const { processed, response } = await handler({ 2 context, 3 request, 4 response: originResponse, 5 quirks: buildNetlifyQuirks(netlifyContext) 6 });
1 if (!processed) { 2 return response; 3 }
1 return new Response(response.body, { 2 ...response, 3 headers: { 4 ...response.headers, 5 "Cache-Control": "no-store, must-revalidate", 6 Expires: "0", 7 }, 8 });
The code above does the following:1}; 2
- Imports the
createEdgeContextClick to copy
,createUniformEdgeHandlerClick to copy
, andbuildNetlifyQuirksClick to copy
functions from the file you copied in the previous step. - Imports the manifest from
contextManifest.jsonClick to copy
. - Checks the
requestClick to copy
method and, if it is not aGETClick to copy
function or if it matches the paths to be ignored, does not execute that method. - Creates
EdgeClick to copy
ContextClick to copy
withmanifestClick to copy
andrequestClick to copy
. - Creates a handler that makes a request to the origin and executes personalization instructions.
- Passes the objects into your handler, one of which is your quirks built from the Netlify context.
- Returns a response.
- Create a file called
netlify.tomlClick to copy
with the code below:1[[plugins]] 2package = "@netlify/plugin-nextjs"
Once you’ve run the command for building your app on the Netlify CLI, Netlify executes the process with the plugin specified in this file, which is1[[edge_functions]] 2path = "/*" 3function = "context-middleware"
Next.jsClick to copy
in your case. Netlify routes all requests to your Edge Functioncontext-middleware.Click to copy
- Confirm if your Edge Function works locally with this command:
netlify devClick to copy
- Run this command to deploy the app to Netlify:
netlify deploy --build --dir .next --prodClick to copy
After deployment is complete, click the site URL to view the app. If I am in Poland at this time, the content for Poland is displayed.
To view the signals and quirks, install the Uniform context extension tools from Chrome.
The image above shows that the is
FromPolandClick to copy
signal has been triggered.If I am in Germany at this time, running the web app on Netlify shows the personalized content for Germany.
And the dimension on Uniform’s
contextClick to copy
extension confirms that.Summing it up
This tutorial describes how to do the following:
- Build a Uniform project, set it up with a Next.js web app, and deploy the app on Netlify.
- Create components and compositions to personalize webpages on Uniform.
- Integrate Netlify Edge Functions for personalization into a project.
Here are a few resources from Uniform’s documentation you might find helpful: