Developer use cases

Learn actions you can take to work with project maps.

The following example demonstrates how to list all compositions that are in a published state using the project map client (there are some exceptions).

In this example Uniform uses Dynamic Catch All Routes from Next.js to render all pages in single place, from Home page, to deep multilevel pages.

/pages/[[...path]].tsx

import { GetStaticPropsContext } from "next"; import { CanvasClient, CANVAS_DRAFT_STATE, CANVAS_PUBLISHED_STATE, } from "@uniformdev/canvas"; const canvasClient = new CanvasClient({ apiKey: "[!!! UNIFORM API KEY !!!]", projectId: "[!!! UNIFORM PROJECT ID !!!]", }); export async function getStaticProps(context: GetStaticPropsContext) { const path = context?.params?.path; const pathString = Array.isArray(path) ? path.join("/") : path; const { preview } = context; const { composition } = await canvasClient.getCompositionByNodePath({ projectMapNodePath: pathString ? `/${pathString}` : "/", state: process.env.NODE_ENV === "development" || preview ? CANVAS_DRAFT_STATE : CANVAS_PUBLISHED_STATE, }); if (!composition) { return { notFound: true }; } return { props: { composition, preview, }, }; }

An example to define the paths of the compositions to be static-generated:

/pages/[[...path]].tsx

import { ProjectMapClient } from "@uniformdev/project-map"; import { GetStaticPaths } from "next"; const projectMapClient = new ProjectMapClient({ apiKey: "[!!! UNIFORM API KEY !!!]", projectId: "[!!! UNIFORM PROJECT ID !!!]", }); export const getStaticPaths: GetStaticPaths = async () => { const { nodes } = await projectMapClient.getNodes({ projectMapId: "[!!! UNIFORM PROJECT MAP ID !!!]", }); return { paths: nodes?.filter((node) => node.compositionId).map((node) => node.path) ?? [], fallback: false, }; };

By using the link parameter on components, editors can set links to other nodes. This ensures valid URLs even when the linked node is moved to another location within the project map. How these links are rendered in the frontend application is up to the developer to decide and implement.

Here is an example of a custom InternalLink component that demonstrates how to render an internal link with the Link parameter using the Next.js Link component and how to handle different link types, including links to project map nodes, URLs, email addresses, and telephone numbers. The LinkParamValue and ProjectMapLinkParamValue types are used to handle the different link types, but most of the time the LinkParamValue type is sufficient. The ProjectMapLinkParamValue type is used when the link is a project map node and you need to access the node id, dynamic input values, or project map id in the component.

/src/components/InternalLink.tsx

import Link from "next/link"; import { LinkParamValue, ProjectMapLinkParamValue } from "@uniformdev/canvas"; export default function InternalLink({ title, internalLink, openInNewTab, }: { title: string; internalLink: LinkParamValue | ProjectMapLinkParamValue; openInNewTab: boolean; }) { // If the link is a project map node, we can get the node id, dynamic input values, and project map id if (internalLink?.type === "projectMapNode") { const nodeId = internalLink.nodeId; const dynamicInputValues = internalLink.dynamicInputValues; const projectMapId = internalLink.projectMapId; } // For all links types, we can get the URL or target from the 'path' property // For example, if no link is set just render an anchor link - change to fit your needs if (internalLink?.type === "projectMapNode" || internalLink?.type === "url") { const url = internalLink?.path?.length ? internalLink.path : "#"; // Render link using Next.js Link Component return ( <Link href={url} target={openInNewTab ? "_blank" : "_self"}> {title} </Link> ); } // If the link is not a project map node or URL (e.g. a telephone number or email address), we can render a regular anchor link if (internalLink?.type === "email" || internalLink?.type === "tel") { return ( <a href={internalLink?.path} target={openInNewTab ? "_blank" : "_self"}> {title} </a> ); } // Optionally, if the link is not an existing link type, we can throw an error throw new Error(`Unsupported link type: ${internalLink?.type}`); }

Usually sitemap generation is your web framework task (also more reliable because of dynamic pages exceptions). But you can use Project Map API directly to fetch whole tree (tree or flat structure) data to construct it your self.

Next.js framework provides three ways to achieve it:

  1. Manual static file upload - https://nextjs.org/learn/seo/crawling-and-indexing/xml-sitemaps
  2. Generate via getServerSideProps - https://nextjs.org/learn/seo/crawling-and-indexing/xml-sitemaps. This is the only option where you'd need to use Project Map API
  3. Preferred option: Generate via npm package next-sitemap.

The tree-like hierarchical structure of nodes in a project map make it easy to render navigational components using the project map client. Example use cases for navigation could be:

  • Global navigation: Use project maps to render a main navigation or footer navigation
  • Local navigation: Show links to sibling or child nodes of a specific node.
  • Breadcrumb navigation: Show the trail of parent nodes of a specific node (such as Home > Company > About us).

Advanced or custom navigation

If you need to control the structure or display, or need to enrich your navigation with content or personalized links, Uniform recommends you create custom Canvas components that represent your navigation UI. This gives you a high level of flexibility as you can leverage all Canvas capabilities. Use link parameters in your components to connect your links with the project map.

To get the list of the top level project map nodes (for example, for a global navigation), you can use ProjectMapClient.getNodes and set the depth parameter to 1:

const projectMapClient = new ProjectMapClient({ apiKey: "[!!! UNIFORM API KEY !!!]", projectId: "[!!! UNIFORM PROJECT ID !!!]", }); const response = await projectMapClient.getNodes({ depth: 1, });

To get the ancestors of a project map node (for example, for breadcrumb navigation), you can use ProjectMapClient.getNodes and set the includeAncestors parameter to true:

const projectMapClient = new ProjectMapClient({ apiKey: "[!!! UNIFORM API KEY !!!]", projectId: "[!!! UNIFORM PROJECT ID !!!]", }); const response = await projectMapClient.getNodes({ path: "/my-category/my-subcategory/my-page", includeAncestors: true, });