Skip to main content

Routing

This section provides information on how to manage routing for front end applications built on Uniform.

Routing is the process of resolving the URL of a request and resolving it to be able to render the appropriate content for that URL.

info

Many modern front end frameworks (like Next.js, Nuxt 3, Remix) take a file-system based routing apporach.

To ensure that you can render pages correctly you need to define routes in your application that match or capture URL patterns that are created in Canvas.

One key aspect of routing is to ensure that valid URLs are created, used and maintained. Once a URL is published it acts as a contract with any visitor that content can be retrieved using it. Website owners also don't have any control how and where the URL is used once it is on the web.

Therefore it is important to make decisions about what system or URL management approach acts as the source of truth for URLs to ensure that they are consistent and valid.

URL management approaches

There are two main approaches for managing URLs in Uniform:

  1. using project map: best used when Uniform should act as the primary source of truth for URLs
  2. using composition slugs (Legacy): best used when other systems or tools act as the source of truth for URLs

Project map

A project map has a tree of nodes that define the URL structure of your web pages.

By using Project maps you can manage, visualize and easily resolve URLs for pages that are managed in Uniform (using compostion nodes) or in other systems (using placeholder nodes or dynamic pages).

Choose the Project map approach for managing URLs when:

  • Uniform Canvas is responsible for defining and resolving most of your URLs
  • your application has nested URLs or views
  • your application has more than a few pages or views
  • you want to empower you business users to define URL structures
Project map nodes vs composition slugs

When attaching a composition to a project map node the slug field of the composition should not be used to define the URL path of that composition.

For the majority of cases the slug field is then no longer needed. But in some cases the slug field can be used as a human-readable identifier to make make it easier to fetch the composition in code: E.g. when the composition only represents part of a page (e.g. site header) and is not attached to a project map node.

Learn more about how to use the project map.

These tools can be used for implementing routing with Project maps:

Using Project map with Next.js

If project map controls all URLs in your Next.js project, then use Dynamic Route with optional "Catch all". That will cover any URL in a project map as well as a home page. Then, provide a list of all nodes from the project map as list of static paths.

./pages/[[...path]].jsx
import {
UniformComposition,
UniformSlot,
} from "@uniformdev/canvas-react";
import { withUniformGetStaticProps, withUniformGetStaticPaths } from '@uniformdev/canvas/project-map';
// as the file is called '[[...path]].jsx' you have to pass "path" as param
export const getStaticProps = withUniformGetStaticProps({ param: 'path'});
export const getStaticPaths = withUniformGetStaticPaths();

export default function Page({ data }) {
return (
<main>
<UniformComposition data={data}>
<UniformSlot name="header" />
<UniformSlot name="content" />
<UniformSlot name="footer" />
</UniformComposition>
</main>
)
}
tip

Make sure the UNIFORM_PROJECT_MAP_ID .env variable exists.

Composition slugs

Legacy approach

With the introduction of Project Maps the routing approach using composition slugs is no longer the recommended way of managing URLs in Uniform.

While it is still supported and useful in certain scenarios, we recommend using Project Maps for routing, linking and URL management.

The slug-based solution relies on the "slug" field in a composition that stores either a full URL or a unique pathname. If the slug field contains a full, unique URL, all pages are generated by using Canvas API.

In some cases the slug of a composition only contains the final path segment of the URL and the full URL is assembled and resolved based on some conventions. E.g. a composition of type Product Feature Page would only have my-awesome-shirt as the slug but the full URL would be /products/features/my-awsome-shirt.

Choose the slug-based approach for managing URLs when:

  • another system is responsible for defining and resolving URLs (e.g. another CMS or a front end framework)
  • your application has no need for nested URLs or views
  • your application is very basic and only has very few pages or views
  • you need to fetch compositions that are not attached to a project map node

Using composition slugs with Next.js

If every composition has a unique URL slug specified, then use Dynamic Route with optional "Catch all" to cover any slug and the home page.

./pages/[[...slug]].jsx
import {
UniformComposition,
UniformSlot,
} from "@uniformdev/canvas-react";
import { withUniformGetStaticProps, withUniformGetStaticPaths } from '@uniformdev/canvas/slug';
export const getStaticProps = withUniformGetStaticProps({ param: 'slug'});
export const getStaticPaths = withUniformGetStaticPaths();

export default function Page({ data }) {
return (
<main>
<UniformComposition data={data}>
<UniformSlot name="header" />
<UniformSlot name="content" />
<UniformSlot name="footer" />
</UniformComposition>
</main>
)
}

Next.js helper methods for routing

The Uniform @uniformdev/canvas-next package provide some helper methods to help cut down on boilerplate code to implement routing in Next.js.

Add npm package

  1. Open a terminal in the root of the repository.

  2. Enter the following command:

    npm install @uniformdev/canvas-next

Common parameters

preview

Flag to use draft content from Canvas. It can be overridden with { preview: process.env.development || process.env.STAGE_ENV === '1' } to build pages with compositions still in draft if it's local .env or dedicated private env for internal testing.

client

Use to override API client and its properties (for example, to override the https agent).

import https from 'https';
import { CanvasClient } from '@uniformdev/canvas';
import { withUniformGetStaticPaths } from '@uniformdev/canvas-next/slug';
// Create https agenct instance that ignores SSL errors
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
});
// Use that agent only in this custom fetch override
const customFetch = (...args) => fetch(args[0], { ...args[1], agent: httpsAgent });
const client = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
apiHost: process.env.UNIFORM_CLI_BASE_URL,
// For debugging purpose lets bypass CDN
bypassCache: true,
fetch: customFetch,
});
export const getStaticPath = withUniformGetStaticPaths({ client });

requestOptions

Override almost any parameter in an API call inside the helper. The request options available will depend on the type of helper you are using.

import { withUniformGetStaticPaths } from '@uniformdev/canvas-next/project-map';
export const getStaticPaths = withUniformGetStaticPaths({
requestOptions: {
depth: 5,
withCompositionData: true
}
});

modifyPath

An optional mapping function that you can use to modify the path passed to the API. It is not available in the withUniformGetStaticPaths helper.

import { withUniformGetServerSideProps } from '@uniformdev/canvas-next/project-map';
export const getServerSideProps = withUniformGetServerSideProps({
modifyPath: (path) => `/uniform${path}`
});

param

Available in withUniformGetStaticProps helper, it specifies name of the parameter that contains pathname or slug, depending on whether you are using the "project map" or "slug" approach respectively. It needs to match Dynamic route segments. For a structure like the ./pages/[[...myParam1]].jsx page you would use { param: 'myParam1' }. Structures like withUniformGetServerSideProps instead rely on the full path received. Use prefix or modifyPath to control that behavior.

Project map withUniformGetStaticPaths

Fetch and retrieve all or only specified nodes and return an array.

rootPath

By default, it's /, so all nodes from the project map are used. You can override this if you want to cover only a section of your website. Here is an example structure:

__ Home /
|__ Events /events
|__ Chistmas Eve /events/chistmas
|__ New Year /events/new-year
|__ ... /events/...
|__ About Us /about-us
|__ Contacts /about-us/contacts
|__ Team /about-us/team
|__ Career /about-us/career
|__ Code of Conduct /about-us/code-of-coduct

If you are building page in Next.js for an "About Us" section, you can specify { rootPath: '/about-us' }.

callback

Alter a list of nodes after fetching and before mapping them into the paths parameter array. For example, this blacklists some URL patterns, because they're covered by dedicated Next.js pages:

import { withUniformGetStaticPaths } from '@uniformdev/canvas-next/project-map';
export const getStaticPaths = withUniformGetStaticPaths({
callback: async (nodes) => {
const skipPathNames = [
'/products/', // product pages
'/about-us/legal', // these are all external pdf links but still should be in project map
];
return nodes.filter((node) => !skipPathNames.find((path) => node.path.startsWith(path)));
}
});

prefix

A string that you want prepended to paths returned by your project map. Useful when calling from a nested folder which is not part of your project map structure.

Project map withUniformGetServerSideProps and withUniformGetStaticProps

Returns getServerSideProps and getStaticProps compatible callbacks respectively.

callback

Alter Props object and composition data or inject additional data props.

import { enhance } from '@uniformdev/canvas'
import { withUniformGetStaticProps } from '@uniformdev/canvas-next/project-map';
import { enhancers } from '../src/lib/enhancers';
export const getStaticProps = withUniformGetStaticProps({
callback: async (context, composition) => {
// If you need to run uniform enhancers here is a place
if (composition) {
await enhance({ composition, enhancers, context });
} else {
return {
notFound: true,
}
}
// If you want to change properties structure or wrap composition data with some sort of state management
const store = createReduxOrSmthStore({ initialData: { composition }});
return {
props: {
// Enhanced composition data will be injected later, so no need to do it yourself
// uniformData: composition,
myCustomProp: 'foo',
// Provide initial state for store to be hydrated on client side
initialStoreState: store.getState(),
},
// Specifying some NextJS ISG params per page.
revalidate: 100,
};
}
);

Project map withUniformGetServerSideProps

prefix

A string that you want to strip from the resolved context.resolvedUrl. Useful when calling from a nested folder which is not part of your project map structure

tip

Learn more about enhancing.

Slug (legacy) withUniformGetStaticPaths

callback

Alter a list of nodes after fetching and before they're mapped into the paths params array. For example, this blacklists some URL patterns because they're covered by dedicated Next.js pages:

import { withUniformGetStaticPaths } from '@uniformdev/canvas-next/slug';
export const getStaticPaths = withUniformGetStaticPaths({
callback: async (compositions) => {
const skipSlugs = [
'mobile-view', // composition only used for mobile app
'internal-test', // some other specified slug not to be generated
];
return compositions.filter((composition) => !skipSlugs.includes(composition.composition._slug));
}
});

Slug (legacy) withUniformGetServerSideProps and withUniformGetStaticProps

callback

Alter Props object and composition data.

import { enhance } from '@uniformdev/canvas'
import { withUniformGetStaticProps } from '@uniformdev/canvas-next/project-map';
import { enhancers } from '../src/lib/enhancers';
export const getServerSideProps = withUniformGetStaticProps({
callback: async (context, composition) => {
if (composition) {
await enhance({ composition, enhancers, context });
} else {
return {
notFound: true,
}
}
return {
// Enhanced composition data will be injected later, so no need to do it yourself
props: {
myCustomProp: 'foo',
},
// Specifying some NextJS ISG params per page.
revalidate: 100,
};
}
);

Slug (legacy) withUniformGetServerSideProps

prefix

A string that you want to strip from the resolved context.resolvedUrl. Useful when calling from a nested folder which is not part of your slug structure