31 min read
Guides/Building websites with Uniform: a beginner's guide
Building websites with Uniform: a beginner's guide
Given today’s ever-dynamic interactions among content managers, content architects, and developers, optimizing workflows for rapid development and iteration is crucial. Hence the birth of digital experience composition (DXC).
This tutorial steps you through the process of generating a SaaS landing page for a fictional ride-sharing application called Joyride. You’ll learn how to build a personalized website powered by Uniform, a DXC Platform (DXCP), with content from Sanity, a modern content management system (CMS). You create the front end with Gatsby.js, a React.js framework for constructing high-performance and SEO-friendly websites.
The concepts, architecture, and methods described reflect the foundation of and apply to Uniform-based development efforts.
Understanding the way DXC works
A DXC platform offers an abstraction layer for all content and composable services—a single source of truth of content—along with a no-code interface for managing that content by practitioners and business users.
CMSes, digital asset-management systems, analytics tools, product information management (PIM) systems, headless commerce systems, etc., all connect to the DXC platform, Uniform, with the resulting data piped structurally and rendered through a front end or presentation layer.
This tutorial shows you how to perform the following high-level steps, which are typical of building websites with Uniform:
- Set up a CMS instance and application.
- Sign up for a Uniform account and integrate with a CMS.
- Design, source, and model the content.
- Build components and compositions on Uniform.
- Create a Gatsby application.
- Connect Uniform and Gatsby.
- Establish preview in Uniform.
- Personalize the website.
Considering the prerequisites
To be able to follow this tutorial, you must have—
- Knowledge of HTML, CSS, JavaScript, and React.js.
Expertise in Gatsby, though not required, would be an advantage.
Creating a Sanity application
First, create a project with content from Sanity, as follows:
1. Create a project folder on your computer called
joyrideClick to copy
.2. In the
joyrideClick to copy
folder, create a Sanity installation and project called cmsClick to copy
by following these steps in the Sanity documentation.
The rule of thumb is to add core content that’s long-lived and reusable in a CMS, e.g., feature lists, articles, offerings, etc. In a later step, you will, through Uniform, add data that supports the content's presentation and navigation. 3. In the
schemasClick to copy
directory in your Sanity project, create two schema files called genericContentClick to copy
and offerings with the following fields:
1// genericContent.ts
2export default {
3 name: 'genericContent',
4 type: 'document',
5 title: 'Generic content',
6 fields: [
7 {
8 name: 'body',
9 title: 'Body',
10 type: 'text',
11 },
12 ],
13}
14// offering.ts
15export default {
16 name: 'offering',
17 type: 'document',
18 title: 'Offering',
19 fields: [
20 {
21 name: 'offeringName',
22 type: 'string',
23 title: 'Offering Name',
24 },
25 {
26 name: 'offeringImage',
27 type: 'image',
28 title: 'Offering image',
29 },
30 {
31 name: 'offeringSummary',
32 type: 'string',
33 title: 'Offering Summary',
34 },
35 {
36 name: 'offeringIntroduction',
37 type: 'text',
38 title: 'Offering Introduction',
39 },
40 {
41 name: 'offeringDescription',
42 type: 'text',
43 title: 'Offering Description',
44 },
45 ],
46
4. Register the schemas in
schema/index.tsClick to copy
with the code below:
1// schema/index.ts
2import genericContent from './genericContent'
3import offering from './offering'
4export const schemaTypes = [genericContent, offering]
This tutorial does not cover the intricacies of publishing Sanity projects. For details on that topic, see these two articles: one on how Schemas work in Sanity and the other on how to publish content to your Sanity Studio.
Once you’ve published the project, add and publish three
genericContentClick to copy
and four offeringClick to copy
documents. 5. Note your Sanity project ID and dataset, which are different from the deployed CMS interface, from your Sanity account’s dashboard for later use in Uniform.
Click to copy
Creating a Uniform account
Recall that Uniform is a DXC platform with a no-code tool for connecting data sources and composable systems, thus enabling business users to craft digital experiences.
As a first step, sign up for a new Uniform account.
Uniform creates your first project with the essential components, compositions, and personalization entities. You’ll modify that project to fit this tutorial. Now do the following:
Uniform creates your first project with the essential components, compositions, and personalization entities. You’ll modify that project to fit this tutorial. Now do the following:
- Rename your project to Joyride.
- Navigate to the Security tab of your team dashboard to create an API key: Name the key and click Save.
- Note down the new API key for use in your front-end application later.
For further reference, read about Uniform’s advanced project roles and permissions.
Components in Uniform are content agnostic, mirroring those in front-end applications. To create webpages, Uniform uses Components as reusable blocks of content to model your layout.
Separately, Compositions in Uniform assemble Components to create a structured presentation—a webpage in this tutorial.
Uniform ships sample Starters with Components and Compositions, which are displayed in the Integrations menu of your Uniform project.
To integrate with Sanity:
- Click the Integrations tab in your project and select and add the Sanity integration (under Content).
- Configure the Sanity integration by authenticating your account, choosing a project, and specifying the dataset. A Sanity Entry Selector type then becomes available for selection in Components.
Crafting the design and content
Product design is the basis of software development. Here’s the landing-page structure for this project:
- A navigation bar with menu options.
- A hero section with an image and call to action.
- A section with steps to sign up for Joyride.
- A section with a list of the Joyride features.
- A footer.
Modeling the content
Now model the layout and content in Uniform:
1. In your Uniform project, navigate to Canvas, where content modeling and page building occur.
2. Optional. Delete the Compositions and Components shipped with the starter if you prefer not to modify them for your project.
A brief background:
- Components in Uniform contain parameters and slots. Uniform parameters, which correspond to the fields for the actual content, are similar to component props in React, e.g., title, description, and link. Uniform slots, which are containers for other child components, are similar to the children component prop in React.
- Compositions in Uniform, which are also components, are root components that form the basis of other components. When creating a Component, checking the Composition Component box marks that Component as a Composition. You can create Components in a different form with Variants.
3. Create the Components with the schedule below. Apply any Component icon you prefer.
- Name: Call to Action Parameters and type
- Title: text
- Link: text
- Name: Generic Card
- Parameters and type
- Title: text
- CTA Link: text
- CTA Title: text
- Body: Sanity Entry Selector
- Allowed Content Types:
genericContentClick to copy
- Searchable Fields:
bodyClick to copy
- Name: Generic Grid Parameters and type
- Title: text
- Slots
- Name: Items
- Required Component Quantity: Minimum 3 (no maximum)
- Allowed Components: Generic Card, Offering Card, Hero
- Name: Hero
- Parameters and type
- Title: text
- Description: text
- Image: text
- Slots
- Name: CTAs
- Allowed Components: Call to Action
- Name: Offering Card
- Parameters and type
- Offering: Sanity Entry Selector
- Allowed Content Types: offering
- Searchable Fields:
offeringNameClick to copy
,offeringImageClick to copy
,offeringSummaryClick to copy
- Name: Offering Grid
- Parameters and type
- Title: text
- Slots
- Name: Offerings
- Required Component Quantity: Minimum 2 (no maximum)
- Allowed Components: Offering Card
- Name: Page
- Composition Component:
trueClick to copy
. (Check the box.) - Parameters & type
- Meta Title: text
- Required:
trueClick to copy
- Slots
- Name: Content
- Allowed Components: All except Page
All public IDs, a
camelCaseClick to copy
version of the name, are automatically generated by Uniform.4. Navigate to the Compositions side menu and create a Composition called
HomepageClick to copy
: Select the PageClick to copy
Composition type, open the Composition, and assign it a slug of homeClick to copy
.homeClick to copy
is a unique identifier for the Composition. You’ll fetch Compositions with a slug in the front end.
5. Save and publish the empty Composition
homeClick to copy
.Creating a Gatsby application
1. In the project directory on your machine, type this command line to spin up a new Gatsby typescript project:
1
2# Install Gatsby CLI
3 npm init gatsby -ts
2. Follow the prompts to scaffold the project. You need not add Sanity here since all content and CMS integrations take place in Uniform only.
3. Follow this guide to install Tailwind in the Gatsby project. In the Gatsby project, you’ll use Tailwind to style your Components.
4. Install Uniform dependencies with this command line in your project:
That command line installs Uniform packages to manage Compositions in Canvas, a React.js software development kit (SDK) for Canvas, a Uniform Context package for managing personalization, a React.js SDK for Context, the Uniform CLI, a Uniform SDK for Sanity and dotenv to manage environment variables in Node.js.
1npm i @uniformdev/canvas @uniformdev/canvas-react @uniformdev/canvas-sanity @uniformdev/cli @uniformdev/context @uniformdev/context-react dotenv
To avoid version-mismatch errors, ensure that the Uniform packages have the same version number.
5. In your project’s root directory, create a
Be sure to add the values you obtained previously from Sanity and Uniform. Variables prefixed with
.envClick to copy
file with the following keys:
1GATSBY_UNIFORM_API_KEY=
2 GATSBY_UNIFORM_PROJECT_ID=
3 GATSBY_SANITY_PROJECT_ID=
4 GATSBY_SANITY_DATASET=
5 UNIFORM_API_KEY=
6 UNIFORM_PROJECT_ID=
GATSBYClick to copy
are required and processed by Gatsby as runtime variables. 6. Start a local development server on
localhost:8000Click to copy
for your Gatsby application by typing in your project directory:
npm run devClick to copy
Creating Components
In the
src/componentsClick to copy
directory of your project, create Components, which would match those on Uniform, as you’ll soon see when connecting your application to Uniform.Tip: To skip this Component-creation step, clone the branch by setting up a base project with the Components in this repository.
Here is the code for the Components:
CTA.tsx
1// src/components/CTA.tsx
2import * as React from "react";
3export const CTA = ({
4 title,
5 link,
6}: {
7 title: string;
8 link: string;
9}) => {
10 return (
11 <div>
12 <a href={link}>
13 <button className="px-4 py-2 bg-[#c98686] rounded">{title}</button>
14 </a>
15 </div>
16 );
17};
Default.tsx
1// src/components/default.tsx
2import * as React from "react";
3export const Default = () => {
4 return (
5 <div>
6 <h1>Default component</h1>
7 </div>
8 );
9};
Footer.tsx
1// src/component/Footer.tsx
2import * as React from "react";
3export const Footer = () => {
4 return (
5 <div className="px-5 py-2 flex text-[#fff4ec] bg-[#2d2d34] font-light text-sm mt-10">
6 <p> Joyride © 2023</p>
7 </div>
8 );
9};
GenericCard.tsx
1// src/components/GenericCard.tsx
2import * as React from "react";
3export const GenericCard = ({
4 title,
5 ctaLink = "#",
6 ctaTitle,
7 body,
8}: {
9 title: string;
10 ctaLink?: string;
11 ctaTitle?: string;
12 body?: string | any;
13}) => {
14 return (
15 <div className="h-full flex flex-col justify-between">
16 <h3 className="text-xl font-medium text-center mb-5">{title}</h3>
17 <p className="px-10">{body.body}</p>
18 <a href={ctaLink}>
19 <p className="text-center mt-5 text-[#c98686] font-semibold">
20 {ctaTitle}
21 </p>
22 </a>
23 </div>
24 );
25}
GenericGrid.tsx
1// src/components/GenericGrid.tsx
2import { Slot } from "@uniformdev/canvas-react";
3import * as React from "react";
4export const GenericGrid = ({
5 title,
6 children,
7}: {
8 title: string;
9 children: any;
10}) => {
11 return (
12 <div className="border-t-2 py-[4em]">
13 <h3 className="text-3xl text-center mb-10 font-medium">{title}</h3>
14 <div className="grid grid-cols-3 gap-5">
15 <Slot name="items" />
16 </div>
17 </div>
18 );
19};
Click to copy
Header.tsx
1 // src/components/Header.tsx
2
3 import * as React from "react";
4
5 export const Header = () => {
6 return (
7 <div className="px-5 py-2 grid gap-2 content-center grid-cols-12 text-[#fff4ec] bg-[#2d2d34] font-light text-sm">
8 <div className="col-span-4 items-center">
9 <p className="font-bold text-lg">Joyride</p>
10 </div>
11
12 <div className="flex space-x-10 col-span-8 justify-end items-center">
13 <p>Get a ride</p>
14 <p>Drive with us</p>
15 <p>About us</p>
16 <p>Safety</p>
17 <button className="px-2 py-1 bg-[#c98686] rounded">Log in</button>
18 </div>
19 </div>
20 );
21 };
Hero.tsx
1 // src/components/Hero.tsx
2 import { Slot } from "@uniformdev/canvas-react";
3 import * as React from "react";
4 type HeroProps = {
5 title: string;
6 description: string;
7 image: string;
8 children?: any;
9 };
10
11 export const Hero = ({ title, description, image, children }: HeroProps) => {
12 return (
13 <div className="grid grid-cols-2 py-[3em]">
14 <div className="mb-5">
15 <h1 className="text-4xl mb-5">{title}</h1>
16 <p className="mb-5">{description}</p>
17 <div>
18 <Slot name="ctas" />
19 </div>
20 </div>
21 <div>
22 <img src={image} width="500px" />
23 </div>
24 </div>
25 );
26 };
Put together the layout in Layout.tsx:
1 // src/components/Layout.tsx
2
3 import * as React from "react";
4 import { Footer } from "./Footer";
5 import { Header } from "./Header";
6
7 export const Layout = (props: any) => {
8 return (
9 <div className="flex flex-col h-screen justify-between">
10 <Header />
11 <div className="mb-auto">{props.children}</div>
12 <Footer />
13 </div>
14 );
15 };
OfferingCard.tsx
1 // src/components/OfferingCard.tsx
2
3 import * as React from "react";
4
5 export const OfferingCard = ({
6 offering: { offeringName, offeringImage, offeringSummary },
7 }: {
8 offering: any;
9 }) => {
10 return (
11 <div className="text-center rounded-md border-2 p-3 h-full flex flex-col justify-between">
12 <p className="text-lg font-semibold">{offeringName}</p>
13
14 <div className="min-h-150">
15 <img
16 src={offeringImage}
17 width="100px"
18 className="mx-auto"
19 height={300}
20 />
21 </div>
22 <p className="text-sm">{offeringSummary}</p>
23 </div>
24 );
25 };
OfferingGrid.tsx
1 // src/components/OfferingGrid.tsx
2
3 import { Slot } from "@uniformdev/canvas-react";
4 import * as React from "react";
5
6 export const OfferingGrid = ({ title }: { title: string; children: any }) => {
7 return (
8 <div className="border-t-2 py-[4em]">
9 <h3 className="text-3xl text-center mb-10 font-medium">{title}</h3>
10 <div className="grid grid-cols-4 gap-7">
11 <Slot name="offerings" />
12 </div>
13 </div>
14 );
15 };
Finally, create a wrapper Component for the page in
Page.tsxClick to copy
with the code below for the individual Gatsby pages.1 // src/components/Page.tsx
2
3 import * as React from "react";
4 import { Layout } from "./Layout";
5
6 export const PageComponent = (props: any) => {
7 return (
8 <Layout>
9 <div className="container mx-auto">{props.children}</div>
10 </Layout>
11 );
12 };
Note theSlotComponentClick to copy
from Uniform, which identifies the containers for child components with Slots with the name prop as specified in Uniform.
Creating a Composition with content in Uniform
1. In Uniform, open the empty Homepage Composition you created earlier.
2. In the Content Slot of the Composition, add Components and their content with the following structure (Components and parameters):
- Component:Hero
- Title: Let’s Take a Ride
- Description: Wherever you want to be, let's take you there. Superfast. We put the Joy in Joyride.
- CTAs
- Component: Call to Action
- Title: Contact us
- Link: #
- Component:Generic Grid
- Title: Find your joy
- Items
- Component: Generic Card
- Title: Get our app
- CTA Title: Learn More
- Body: Pick data from Sanity by following the list as shown.
- Component: Generic Card
- Title: Create account
- CTA Title: Learn More
- Body: Pick data from Sanity by following the list as shown.
- Component: Generic Card
- Title: About Joyride
- CTA Title: Learn More
- Body: Pick data from Sanity by following the list as shown.
- Component: Offering Grid
- Title: Our Offerings
- Offerings
- Component: Offering Card
- Offering: Pick data from Sanity by following the list as shown.
- Component: Offering Card
- Offering: Pick data from Sanity by following the list as shown.
- Component: Offering Card
- Offering: Pick data from Sanity by following the list as shown.
- Component: Offering Card
- Offering: Pick data from Sanity by following the list as shown.
Here’s how the structure looks. Note that the Component structure on the left panel matches the design.
3. Save and publish the Composition.
Connecting the Composition in Gatsby
To use data from Uniform in the front end:
- Fetch Compositions from Uniform by Slug.
- Enhance the Composition (more on that later).
- Create a component-resolution function.
Gatsby touts a file system-based routing and builds files in the src/pages directory as pages. index.tsx is the website’s homepage. By fetching Uniform data at runtime with Gatsby’s server-side rendered (SSR) function, serverData, you can publish updated content on Uniform without rebuilding the static website.
Also, you can implement robust personalization in SSR mode through Uniform.
Do the following:
- Create a function that renders a page with the code below in
src/pages/index.tsxClick to copy
:Depending on your application's architecture, you can fetch Compositions from Uniform in multiple ways. For this tutorial, fetch each Composition on its corresponding page. Another flexible way is to find out the page path and fetch a Composition based on that page. For this tutorial, keep it basic and fetch Compositions based on their Slug.1 import * as React from "react"; 2 import type { 3 PageProps, 4 } from "gatsby"; 5 import { PageComponent } from "../components/Page"; 6 const Homepage = (props: PageProps) => { 7 return ( 8 <PageComponent> 9 {// component to go in here} 10 </PageComponent> 11 ); 12 }; 13 export default Homepage;
- Add the code below to
index.tsxClick to copy
to create a function that fetches a Composition by Slug:1import * as React from "react"; 2 import type { PageProps } from "gatsby"; 3 import { PageComponent } from "../components/Page"; 4 import { CanvasClient } from "@uniformdev/canvas"; 5 // function to get composition 6 export const getComposition = async () => { 7 const client = new CanvasClient({ 8 apiKey: process.env.GATSBY_UNIFORM_API_KEY, 9 projectId: process.env.GATSBY_UNIFORM_PROJECT_ID, 10 }); 11 const { composition } = await client.getCompositionBySlug({ slug: "home" }); 12 return composition; 13 }; 14 const Homepage = (props: PageProps) => { 15 return ( 16 <PageComponent> 17 {// component to go in here} 18 </PageComponent> 19 ); 20 }; 21 export default Homepage;
Enhancing the Compositions
To ensure accuracy, instead of storing actual data from multiple sources, Uniform stores references to the data. Based on the reference passed in the Composition payload, you enhance the data, which converts the reference to the actual content.
For Sanity and other integrations, Uniform has created an enhancer to juice up the fetched Composition with the actual data.
Create a function with the following code in index.js to enhance the composition:
1// Other imports go in here
2import {
3 CanvasClient,
4 ComponentInstance,
5 enhance,
6 EnhancerBuilder,
7} from "@uniformdev/canvas";
8import createSanityClient from "@sanity/client";
9import {
10 CANVAS_SANITY_PARAMETER_TYPES,
11 createSanityEnhancer,
12} from "@uniformdev/canvas-sanity";
13// function to get composition by slug
14export const getComposition = async () => {
15 // function definition goes in here
16};
17// Sanity enhancer function
18export async function enhanceComposition(composition: ComponentInstance) {
19 const sanityClient = createSanityClient({
20 projectId: process.env.GATSBY_SANITY_PROJECT_ID,
21 dataset: process.env.GATSBY_SANITY_DATASET,
22 useCdn: false,
23 });
24 // Create a modified enhancer to enhance the images and return offeringImage
25 const sanityEnhancer = createSanityEnhancer({
26 client: sanityClient,
27 modifyQuery: (options) => {
28 options.query = `*[_id == $id][0] {
29 "offeringImage": offeringImage.asset->url,
30 ...
31 }`;
32 return options;
33 },
34 });
35 await enhance({
36 composition,
37 enhancers: new EnhancerBuilder().parameterType(
38 CANVAS_SANITY_PARAMETER_TYPES,
39 sanityEnhancer
40 ),
41 context: {},
42 });
43}
44const Homepage = (props: PageProps) => {
45 return (
46 <PageComponent>
47 {// Components to go in here}
48 </PageComponent>
49 );
50};
51export default Homepage;
The above block does the following:
- Import all the required modules and methods.
- Create a new instance of the Sanity client with your project ID and dataset to leverage the Sanity enhancer.
- Create an enhancer with the
createSanityEnhancerClick to copy
function. By default, the Sanity enhancer does not enhance images, i.e., the image reference remains as is. Instead, themodifyQueryClick to copy
option modifies the enhancer. The modified query matches Sanity’s query-language syntax, fetching all the data while adding anofferingImageClick to copy
field with an image URL as the value. - Pass the Composition and a prebuilt Sanity enhancer to the enhance function. Calling the
sanityEnhancerClick to copy
function with the raw Composition as an argument enhances it.
Rendering the enhanced Composition
To render the enhanced Composition, do the following:
1. Fetch the composition and run the enhancer on it in Gatsby’s SSR-specific
getServerDataClick to copy
function by adding the code below to the pages/index.tsxClick to copy
file:1import * as React from "react";
2import type {
3 GetServerDataProps,
4 GetServerDataReturn,
5 PageProps,
6} from "gatsby";
7// Other imports go in here
8// function to get composition
9export const getComposition = async () => {
10// function definition goes in here
11};
12// Function to fetch Composition server-side
13export async function getServerData({
14 headers,
15 method,
16 url,
17 query,
18 params,
19}: GetServerDataProps): GetServerDataReturn {
20 const composition = await getComposition();
21 // Enhance composition
22 await enhanceComposition(composition);
23 // Return enhanced composition
24 return {
25 status: 200,
26 props: { composition },
27 };
28}
29// Sanity enhancer function
30export async function enhanceComposition(composition: ComponentInstance) {
31// function definition goes in here
32}
33const Homepage = (props: PageProps) => {
34 const { serverData } = props;
35 return (
36 <PageComponent>
37 {// render components here}
38 </PageComponent>
39 );
40};
41export default Homepage;
The
getServerDataClick to copy
function fetches and enhances the Composition, making the returned data available to the page Component HomepageClick to copy
as serverDataClick to copy
. 2. Create a resolver function that matches a Uniform Component with a Component in your Component library: Add the following code to
pages/index.tsxClick to copy
:1import * as React from "react";
2import type { PageProps } from "gatsby";
3import { PageComponent } from "../components/Page";
4import { Hero } from "../components/Hero";
5import { CTA } from "../components/CTA";
6import { GenericGrid } from "../components/GenericGrid";
7import { GenericCard } from "../components/GenericCard";
8import { OfferingCard } from "../components/OfferingCard";
9import { OfferingGrid } from "../components/OfferingGrid";
10import { ComponentInstance } from "@uniformdev/canvas";
11import { ComponentProps } from "@uniformdev/canvas-react";
12import { Default } from "../components/Default";
13// function to get composition
14export const getComposition = async () => {
15 // Function definition goes in here
16};
17// Function to fetch Composition serverside
18export async function getServerData({
19 headers,
20 method,
21 url,
22 query,
23 params,
24}: GetServerDataProps): GetServerDataReturn {
25 // Function definition goes in here
26}
27// Sanity enhancer function
28export async function enhanceComposition(composition: ComponentInstance) {
29 // Definition goes in here
30}
31// Resolve Render function
32export function componentResolutionRenderer(
33 component: ComponentInstance
34): React.ComponentType<ComponentProps<any>> {
35 switch (component.type) {
36 case "hero":
37 return Hero;
38 break;
39 case "callToAction":
40 return CTA;
41 break;
42 case "genericCard":
43 return GenericCard;
44 break;
45 case "genericGrid":
46 return GenericGrid;
47 break;
48 case "offering":
49 return OfferingCard;
50 break;
51 case "offeringGrid":
52 return OfferingGrid;
53 break;
54 default:
55 return Default;
56 break;
57 }
58}
59const Homepage = (props: PageProps) => {
60 return (
61 <PageComponent>
62 {// Render components here}
63 </PageComponent>
64 );
65};
66export default Homepage;
In the above code, the
componentResolutionRendererClick to copy
function switches the returned Component based on the Component typeClick to copy
.3. Render the Composition with Uniform’s
CompositionClick to copy
Component by adding this code: 1import * as React from "react";
2import type {
3 GetServerDataProps,
4 GetServerDataRetur
5 PageProps,
6} from "gatsby";
7import { ComponentInstance } from "@uniformdev/canvas";
8import {
9 ComponentProps,
10 Composition,
11 Slot,
12} from "@uniformdev/canvas-react";
13// function to get composition
14export const getComposition = async () => {
15 // Definition goes in here
16};
17// Function to fetch Composition serverside
18export async function getServerData({
19 headers,
20 method,
21 url,
22 query,
23 params,
24}: GetServerDataProps): GetServerDataReturn {
25 // definition goes in here
26}
27// Sanity enhancer function
28export async function enhanceComposition(composition: ComponentInstance) {
29 // Definition goes in here
30}
31// Resolve Render function
32export function componentResolutionRenderer(
33 component: ComponentInstance
34): React.ComponentType<ComponentProps<any>> {
35 // Definition goes in here
36}
37const Homepage = (props: PageProps) => {
38 const { serverData } = props;
39 const { composition } = serverData as any;
40 return (
41 <PageComponent>
42 <Composition
43 data={composition}
44 resolveRenderer={componentResolutionRenderer}
45 ></Composition>
46 </PageComponent>
47 );
48};
49export default Homepage;
4. Restart your Gatsby development server for the updated page with the homepage Composition fetched from Uniform and rendered with your local Components.
Uniform renders with the Composition Component. For child elements, rendering is done with named Slot components in the parent Components.
Here’s a GitHub gist on the content of the
index.tsxClick to copy
component.Enabling contextual editing in Uniform
Uniform features a rich editing tool for building interfaces based on the existing layout and components in the front end. To use that tool, first set up the front end to provide context to Uniform.
Available from Uniform are front-end functions that enable contextual editing or, in some cases, bake it into the Canvas package. For React.js, Uniform offers
useContextualEditingClick to copy
in canvas-reactClick to copy
. First, add the code below to
pages/index.tsxClick to copy
to import and set up contextual editing in the renderClick to copy
function of the page:
1import * as React from "react";
2import type {
3 GetServerDataProps,
4 GetServerDataReturn,
5 PageProps,
6} from "gatsby";
7import { PageComponent } from "../components/Page";
8import {
9 ComponentProps,
10 Composition,
11 useContextualEditing,
12} from "@uniformdev/canvas-react";
13// All other imports go here
14// Sanity enhancer function goes here
15// Function to get composition goes here
16// Function to fetch Composition serverside for use in the page component, goes here.
17// Resolve Render function goes here
18const Homepage = (props: PageProps) => {
19 const { serverData } = props;
20 const { composition: initialCompositionValue } = serverData as any;
21 const { composition } = useContextualEditing({
22 initialCompositionValue,
23 enhance: async ({ composition }) => {
24 await enhanceComposition(composition);
25 return composition;
26 },
27 });
28 return (
29 <PageComponent>
30 <Composition
31 data={composition}
32 resolveRenderer={componentResolutionRenderer}
33 ></Composition>
34 </PageComponent>
35 );
36};
37export default Homepage
The block above performs these tasks:
- Set up
useContextualEditingClick to copy
with the initialcompositionClick to copy
value. - Specify the
enhancerClick to copy
function for the Composition. - Pass the new
compositionvalueClick to copy
to the Composition component.
Specifying theenhancerClick to copy
function provides Uniform with the context of what enhancers are in use. Without that information, Uniform’s contextual-editing interface renders Component references instead of the enhanced version.
To complete the contextual-editing setup, specify your website’s preview URL in Uniform. During local development, that URL is the local host, i.e.,
localhost:8000Click to copy
, which requires the local development server to run at all times. Alternatively, specify a URL for a deployed host. Live-preview URLs are an SSR Component of websites that direct the web framework to generate the pages on the server with the updated content. Thus, the logic behind the URL (seen as the entry URL) can handle redirects to other pages.
Different web frameworks create preview URLs in different ways. Uniform provides
slugClick to copy
information as query parameters in the request to the preview URL with which you can manage page redirects.For this tutorial, specify the local development URL without redirects, as follows:
On Uniform, go to Canvas and open the Homepage Composition. Enter the URL in the Preview URL text field
Alternatively, specify the preview URL by first choosing Project dashboard > Settings > Canvas Settings for the dialog box. To avoid CORS errors during composition enhancement, add the
localhostClick to copy
URL to the list of allowed domains in your Sanity admin dashboard.
The webpage is then loaded in the visual panel (midsection) of the Canvas editor.
Editing the structure on the left panel renders immediately in the middle. Selecting an element on the website highlights the corresponding component in the structure panel. See this example:You can now create Compositions on Uniform and compose the Components with the contextual editor.
Personalizing the experience with Uniform Context
Uniform Context is a group of elements and tools for identifying, classifying, personalizing, and analyzing digital experiences. Personalization with Uniform takes two steps:
- Set up the personalization artifacts and criteria on Uniform—usually done by practitioners and business users.
- Set up the front end to recognize Uniform Context.
Setting up Signals in Uniform
The goal of the personalization demo below is to identify and convert potential drivers.
Uniform offers various ways to classify users. For this tutorial, identify a user with Signals and show that individual a unique version of the website's Hero section. Your target users are those who follow a promotion link with the parameter
utm_campaign=driverlaunchClick to copy
.Perform these steps:
1. In your Uniform project, navigate to Personalization > Signals and create a Signal with the name Driver Campaign. Signals have improved scores if users match the specified criteria up to the score's threshold.
2. With the Signal builder, set up a Signal with the criterion Query String named
utm_campaignClick to copy
, which equals driverlaunchClick to copy
, like this:
3. Leave the Signal score and threshold unchanged at 50 and 100, respectively.
4. Save and close the new Signal.
As is obvious on the interface, you can chain Signal criteria.
5. In the personalization dashboard, click Publish in the top-right corner to publish all Context entities, including Personalization.
Be sure to publish each time you change a Context entity.
6. In the Canvas editor, personalize the Hero component with a different title, description, and CTA: Hover below the Content slot in the structure panel and add a Personalization component.
In the right panel, add an analytics name as the personalization element when the data is sent to your configured analytics tools. Leave empty the Number of variations to show field. That’s the number of personalized variants—with a default of 1—to show at a time.
Since you specified the Personalize Component as being allowed while defining the Page Component's Slot, you can add that Component to the Content Slot.
7. Add the following content to two new Hero Component variants in the Personalized Variations slot of the structure panel.
- Component: Hero
- Title: Ride for us
- Description: We don't just move people around. We move feelings and create lasting memories for people. Come join us to deliver joy!
- CTAs
- Component: Call to Action
- Title: Work with us
- Link: #
- Component: Hero
- Title: Let’s Take a Ride
- Description: Wherever you want to be, let's take you there. Superfast. We put the joy in Joyride.
- CTAs
- Component: Call to Action
- Title: Contact us
- Link: #
8. Assign a Signal to the variant "Ride for us”: Click the variant in the structure pane and, in the editing pane on the right, click the Context tab.
9. Click Add Criteriaand select the Driver Campaign Signal that exceeds a score of 50.
Users who match that Signal see this version of the hero.
10. Save the changes. Leave the context tab of the second Hero variant, Let's take a Ride, as is. That’s the default variant presented to users who don't meet the Signal criteria.
11. Delete the unpersonalized Hero Component you added previously.
12. Save and publish the composition.
The visual pane in the middle is now broken because you must design the website to render personalized components. The next section shows you how to do that.
Personalizing the front end
While configuring personalization on Uniform, set it up in Gatsby so that Uniform renders the Composition with the right Components.
Since you’ve already installed context packages, update the package.json file to fetch Uniform's manifest that contains all Uniform Context configurations and setups, and save the manifest file to a designated folder. Do the following:
1. Update the package.json file's script entries with the code below:
1{
2 [...]
3 "scripts": {
4 "dev": "yarn uniform:download-manifest && gatsby develop",
5 "build": "yarn uniform:download-manifest && gatsby build",
6 "uniform:download-manifest": "uniform context manifest download --output ./uniform-manifest.json"
7 },
8[...]
9}
2. Download the manifest file to the project's root directory with the
uniform:download-manifestClick to copy
script. Rerunning that script overwrites the manifest file.
3. Download the manifest file on dev and build commands.
4.,Rerun the dev command to download the manifest file.
Your Uniform API key needs the correct permissions to download the manifest file. In case you encounter an error on lack of authorization in step 4, update the API key’s permissions in your Uniform account.
Set up Context on the pages by wrapping each page with the
UniformContextClick to copy
component by means of the Gatsby-specific wrapPageElementClick to copy
API in the project's root files, gatsby-browser.tsxClick to copy
and gatsby-ssr.tsxClick to copy
, like this:1// gatsby-browser.tsx
2import "./src/styles/global.css"; // tailwind required
3import { UniformContext } from "@uniformdev/context-react";
4import {
5 Context,
6 enableContextDevTools,
7 ManifestV2,
8} from "@uniformdev/context";
9import manifest from "./uniform-manifest.json";
10import { GatsbyBrowser } from "gatsby";
11import * as React from "react";
12const context = new Context({
13 defaultConsent: true,
14 plugins: [enableContextDevTools()], // Use this to enable the Chrome plugin
15 manifest: manifest as ManifestV2,
16});
17export const wrapPageElement: GatsbyBrowser["wrapPageElement"] = ({
18 element,
19}) => {
20 return <UniformContext context={context}>{element}</UniformContext>;
21};
22Footer
23// gatsby-ssr.tsx
24import "./src/styles/global.css";
25import { UniformContext } from "@uniformdev/context-react";
26import {
27 Context,
28 enableContextDevTools,
29 ManifestV2,
30} from "@uniformdev/context";
31import manifest from "./uniform-manifest.json";
32import type { GatsbySSR } from "gatsby";
33import * as React from "react";
34const context = new Context({
35 defaultConsent: true,
36 plugins: [enableContextDevTools()], // Use this to enable the Chrome plugin
37 manifest: manifest as ManifestV2,
38});
39export const wrapPageElement: GatsbySSR["wrapPageElement"] = ({ element }) => {
40 return <UniformContext context={context}>{element}</UniformContext>;
41};
You’ve now imported the required modules in both files, instantiated the Uniform context, specified the downloaded manifest file, and, as an optional step, enabled the Chrome plug-in, with which you can simulate context scenarios without user input.
In addition, since you’ve fetched Compositions with Gatsby's
getServerDataClick to copy
function, you’ve wrapped all the pages with the UniformContextClick to copy
Component in SSR mode.
6. Restart your development server. To display a personalized homepage, click the link with the UTM parameter localhost:8000?utm_campaign=driverlaunchClick to copy
. Subsequently, content updates or personalization entities from business users are immediately served to end-users on a new build.
The personalized variants on Uniform are displayed in the Canvas editor's visual pane. Clicking a variant renders it.
For further reference, see this version of the site deployed to Gatsby Cloud and the project on GitHub.
Taking the next steps
To recap, here are the three basic steps for setting up a website connected to Uniform with content from Sanity:
- Create Components and a Composition on Uniform.
- Connect it to a Gatsby and Sanity project.
- Set up the page's personalization and contextual editing on Uniform.
Robust websites require multiple pages and routes. You can abstract and reuse several functions described in this tutorial.
Ready to improve the website you just built? See below.
- Create more routes, fetch the Compositions in each of the pages, and render them on the page.
- Manage the hierarchy and structure for your pages with Uniform’s Project Map.
- Create more Signal criteria or Context entities.
- Deploy your website to a hosting provider.
After a content update, you can install Uniform’s Vercel and Netlify plug-ins and start new static builds with webhooks.
Have fun!