Rendering

In this section, you learn how to render components and compositions that are defined using Uniform Canvas in your front-end application.

This usually involves these steps:

  1. Initial project setup for Uniform
  2. Render compositions and slots
  1. Click the Uniform logo at the top of the page to return to the Uniform team home page.

  2. In the Security section, click API keys.

  3. Create an API key with the following custom permissions:

    Uniform Canvas > Compositions > Read Published

    About this step

    If you already have an API key with these permissions, you can skip this step.

  4. Add the following values to your .env file. You collected these values when you created the Uniform API key:

    Uniform valueEnvironment variable
    API KeyUNIFORM_API_KEY
    Project IDUNIFORM_PROJECT_ID

Most modern front-end frameworks like Next.js and Nuxt 3 provide dynamic routing capabilities that are ideal for rendering compositions.

By leveraging dynamic routes, you can create a single file such as /pages/[...path].jsx that handles composition rendering for any URL path. This pattern provides a clean and maintainable way to serve compositions across your entire application.

It also empowers content editors to independently manage the site structure through project maps in Uniform. Editors can create, modify, and reorganize pages without requiring developer assistance for sitemap changes.

When rendering a composition you will need to resolve the current URL to fetch the corresponding composition data.

Refer to the routing guide for more information on how to implement routing using Uniform.

Routing

Learn about routing approaches with Uniform.

Routing with Next.js

Helper methods to simplify routing with Uniform in Next.js.

To make these instructions easier to follow, a specific example is used.

The example activates Canvas for the following dynamic Next.js page (using the page router and server-side rendering), where the content of the <main> tag will be rendered with Canvas:

/pages/[...path].jsx

import Head from "next/head"; export async function getServerSideProps() { return { props: { title: "Sample app", message: "Hello", }, }; } export default function Page({ title, message }) { return ( <div> <Head> <title>{title}</title> </Head> <main> <h1>{message}</h1> </main> </div> ); }
  1. Add the following packages to your app:

    @uniformdev/canvas @uniformdev/canvas-react @uniformdev/canvas-next
  2. Render the page with the composition data:

    1. Add the following code which fetches the composition data for the current route using the Uniform Route API and passes the data to the page component as data prop which is then passed to the UniformComposition component.

      /pages/[...path].jsx

      import Head from "next/head"; import { withUniformGetServerSideProps } from "@uniformdev/canvas-next/route"; import { UniformComposition, UniformSlot } from "@uniformdev/canvas-react"; // this example is using server-side rendering (SSR) for simplicity export const getServerSideProps = withUniformGetServerSideProps({ handleComposition: async ({ compositionApiResponse }) => { const { composition } = compositionApiResponse || {}; return { props: { title: "Sample app", data: composition }, }; }, }); export default function Page({title, data: composition }) { return ( <UniformComposition data={composition}> <div> <Head> <title>{title}</title> </Head> <main> <UniformSlot name="body" /> </main> </div> </UniformComposition> ); }

Next.js Starter: App Router

Check out the Next.js Starter for App Router on GitHub for a complete example.

Next.js Starter: Page Router

Check out the Next.js Starter for Page Router on GitHub for a complete example.

Next.js tutorial: App Router

A step-by-step tutorial on how to build a Next.js app with App Router with Canvas.

Next.js tutorial: Page Router

A step-by-step tutorial on how to build a Next.js app with Page Router with Canvas.

To make these instructions easier to follow, a specific example is used. The example activates Canvas for the following dynamic page, where the content of the <main> tag will be rendered with Canvas:

/pages/[...path].vue

<script lang="ts" setup> const title = 'Sample App'; const message = 'Hello'; </script> <template> <div> <Head> <Title>{{ title }}</Title> </Head> <main> <h1>{{ message }}</h1> </main> </div> </template>
  1. Add the following packages to your app:

    @uniformdev/canvas @uniformdev/context @uniformdev/canvas-vue @uniformdev/context-vue @uniformdev/uniform-nuxt
  2. Add the Nuxt module to nuxt.config.ts:

    nuxt.config.ts

    export default defineNuxtConfig({ modules: ["@uniformdev/uniform-nuxt"], uniform: { projectId: process.env.UNIFORM_PROJECT_ID, readOnlyApiKey: process.env.UNIFORM_API_KEY, }, });
  3. Render the page with the composition data:

    Update the page component in /pages/[...path].vue:

    /pages/[...path].vue

    <script lang="ts" setup> import { DefaultNotImplementedComponent } from '@uniformdev/canvas-vue'; const resolveRenderer = ({ type }) => { // TODO: extend the function to return a component based on the `type` property. return DefaultNotImplementedComponent; }; const title = 'Sample App'; // get the path from the route const route = useRoute(); const path = Array.isArray(route.params.path) ? `/${route.params.path.join("/")}` : `/${route.params.path}`; // fetch the composition const { composition, error } = await useUniformComposition({ projectMapNodePath: path, }); // handle any errors if (error?.value) { console.error("Error fetching composition from Uniform", error.value); } </script> <template> <UniformComposition :data="composition" :resolve-renderer="resolveRenderer"> <div> <Head> <Title>{{ title }}</Title> </Head> <main> <UniformSlot name="body" /> </main> </div> </UniformComposition> </template>

Nuxt 3 Starter

Check out the Nuxt 3 Starter on GitHub for a complete example.

Nuxt 3 tutorial

A step-by-step tutorial on how to build a Nuxt 3 app with Canvas.

During the composition rendering process, Uniform generates markup. By default, the markup is surrounded by a <div> tag. This tag can cause problems with certain forms of CSS.

This example demonstrates how to prevent this extra markup from being generated.

import Head from 'next/head' import { UniformComposition, UniformSlot } from '@uniformdev/canvas-react' function resolveRenderer(component) { ... } export default function Home({ composition }) { return ( <UniformComposition data={composition} resolveRenderer={resolveRenderer} behaviorTracking="onLoad"> <div> <Head> <title>Example</title> </Head> <main> <UniformSlot name='body' /> </main> </div> </UniformComposition> ) }

When a Canvas component has slots, it's expected that the components in the slot will be rendered in the front-end application. Each Canvas component must be mapped to a component type that's compatible with the front-end technology used. You must provide the mapping for Uniform.

This example demonstrates how to define the mapping and assign it to the component that controls the rendering process.

HeroComponent.jsx

import { registerUniformComponent } from "@uniformdev/canvas-react"; export default function HeroComponent() { return <div>Hero Component Content</div> } registerUniformComponent({ type: 'hero', component: HeroComponent, });

As <UniformSlot /> component doesn't import FE components directly, we need to import them manually here to include them to the bundle and run our registerUniformComponent()

import './HeroComponent'; // Optionally you can override default fallback component - DefaultNotImplementedComponent // Useful when your component mapping is not simple 1-to-1 relation import { registerUniformComponent, NOT_IMPLEMENTED_COMPONENT } from "@uniformdev/canvas-react"; const MyNotImplementedComponent = (props) => { return ( <div>COMPONENT {<code>{props.component.componentType}</code>} NOT IMPLEMENTED</div> ); } registerUniformComponent({ type: NOT_IMPLEMENTED_COMPONENT, component: MyNotImplementedComponent, });

You still need to import canvasComponents.js to include all the components, such as in NextJS _app.js is the best place for it.

import '../components/canvasComponents';
import Head from 'next/head' import { CanvasClient } from '@uniformdev/canvas' import { UniformComposition, UniformSlot, DefaultNotImplementedComponent } from '@uniformdev/canvas-react' export async function getStaticProps() { const client = new CanvasClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }) const { composition } = await client.getCompositionBySlug({ slug: "/", }) return { props: { title: "Example page", composition, } } } function resolveRenderer({ type }) { const components = { // 'my_type': MyComponent, }; return components[type] ?? DefaultNotImplementedComponent; } export default function Home({ title, composition }) { return ( <UniformComposition data={composition} resolveRenderer={resolveRenderer}> <div> <Head> <title>{title}</title> </Head> <main> <UniformSlot name='body' /> </main> </div> </UniformComposition> ) }

info

For more information, see the UniformComposition component reference.

A slot lets you define a list of child components to render inside a <UniformSlot /> component.

By default, an array of children renders inside a minimal container (in case of React, it's React.Fragment).

React

function Slide({ title, description, imageUrl }) { return ( <div className="slide"> <img src={imageUrl}/> <h4>{title}</h4> <p>{description}</p> </div> ) }; function Slider({ title }) { return ( <div className="slider"> <h3>{title}</h3> <div className="slides"> <UniformSlot name='slides' /> </div> </div> ) }

The final React output will look like this:

React

<div class="slider"> <h3>Slider title</h3> <div className="slides"> <> <div class="slide"> <img src="image-url-1"/> <h4>Slide 1</h4> <p>Slide 1 description</p> </div> <div class="slide"> <img src="image-url-2"/> <h4>Slide 2</h4> <p>Slide 2 description</p> </div> </> </div> </div>

Sometimes you may want to control how a slot renders its children. For example, the popular slider library Swiper requires you to wrap slides with <SwiperSlide /> component. Each slide should be the immediate child of the parent <Swiper /> component. To achieve this in Uniform you can use the wrapperComponent prop of the <UniformSlot /> component.

React

import { Swiper, SwiperSlide } from 'swiper/react'; function Slide({ title, description, imageUrl }) { return ( <> <img src={imageUrl}/> <h4>{title}</h4> <p>{description}</p> </> ) }; function SliderWrapperComponent({ items }) { return ( <Swiper> {items.map((item, index) => ( <SwiperSlide key={index}> {item} </SwiperSlide> ))} </Swiper> ) } function Slider({ title }) { return ( <div className="slider"> <h3>{title}</h3> <div className="slides"> <UniformSlot name='slides' wrapperComponent={SliderWrapperComponent} /> </div> </div> ) }

info

For more information, see the UniformComposition component reference, and the guide for routing with Next.js.