Uniform how-to/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.
  • A machine fitted with Node.js and its package manager npm or the npm alternative Yarn.
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:
  1. Rename your project to Joyride.
  2.  Navigate to the Security tab of your team dashboard to create an API key: Name the key and click Save
  3. 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:
  1. Click the Integrations tab in your project and select and add the Sanity integration (under Content). 
  2. 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:
1npm i @uniformdev/canvas @uniformdev/canvas-react @uniformdev/canvas-sanity @uniformdev/cli @uniformdev/context @uniformdev/context-react dotenv
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.
To avoid version-mismatch errors, ensure that the Uniform packages have the same version number.
5. In your project’s root directory, create a .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=
Be sure to add the values you obtained previously from Sanity and Uniform. Variables prefixed with 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 &copy; 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 the SlotComponentClick 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
  • 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:
  1. Fetch Compositions from Uniform by Slug.
  2. Enhance the Composition (more on that later).
  3. 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:
  1. Create a function that renders a page with the code below in src/pages/index.tsxClick to copy:
    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;
    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.
  2. 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:
  1. Import all the required modules and methods. 
  2. Create a new instance of the Sanity client with your project ID and dataset to leverage the Sanity enhancer. 
  3. 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, the modifyQueryClick to copy option modifies the enhancer. The modified query matches Sanity’s query-language syntax, fetching all the data while adding an offeringImageClick to copy field with an image URL as the value. 
  4. 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:
  1. Set up useContextualEditingClick to copy with the initial compositionClick to copy value. 
  2. Specify the enhancerClick to copy function for the Composition. 
  3. Pass the new compositionvalueClick to copy to the Composition component.
Specifying the enhancerClick 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:
  1. Set up the personalization artifacts and criteria on Uniform—usually done by practitioners and business users. 
  2. 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.
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:
  1. Create Components and a Composition on Uniform.
  2. Connect it to a Gatsby and Sanity project. 
  3. 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.
  1. Create more routes, fetch the Compositions in each of the pages, and render them on the page.
  2. Manage the hierarchy and structure for your pages with Uniform’s Project Map
  3. Create more Signal criteria or Context entities. 
  4. 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!