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.
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:
- using project map: best used when Uniform should act as the primary source of truth for URLs
- 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
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:
- The Project Map API and Project Map client provides set of methods to fetch URLs in very flexible ways.
- The Canvas client makes it easy to resovle compositions based on node URLs.
Using Project map with Next.js
- 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.
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>
)
}
Make sure the UNIFORM_PROJECT_MAP_ID
.env variable exists.
Composition slugs
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
- 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.
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
Open a terminal in the root of the repository.
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
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