Project Map Client SDK

The Project Map Client (ProjectMapClient from @uniformdev/project-map) provides programmatic access to Uniform's project map -- the hierarchical site structure that maps URL paths to compositions. Use it for building navigation menus, generating sitemaps, resolving locale-specific paths, and working with site structure data.


npm install @uniformdev/project-map

import { ProjectMapClient } from "@uniformdev/project-map"; const projectMapClient = new ProjectMapClient({ apiKey: process.env.UNIFORM_API_KEY, apiHost: process.env.UNIFORM_CLI_BASE_URL || "https://uniform.global", projectId: process.env.UNIFORM_PROJECT_ID, });

The SDK provides a pre-configured client via getProjectMapClient:

import { getProjectMapClient } from "@uniformdev/next-app-router"; const projectMapClient = getProjectMapClient({ cache: { type: "force-cache" }, });

A Uniform project can have multiple project maps. Retrieve the available definitions:

const definitions = await projectMapClient.getProjectMapDefinitions(); for (const pm of definitions.projectMaps) { console.log(pm.id, pm.name); }

Get a specific definition by ID:

const definition = await projectMapClient.getProjectMapDefinition({ projectMapId: "your-project-map-id", });

getNodes returns project map nodes in a flat list format. This is useful for sitemaps, breadcrumbs, or any scenario where you need a flat list of pages:

const response = await projectMapClient.getNodes({ projectMapId: "your-project-map-id", }); for (const node of response.nodes ?? []) { console.log(node.path, node.name, node.type); }

Each ProjectMapNode contains:

PropertyTypeDescription
idstringUnique node identifier
pathstringURL path (e.g., /about, /blog/:slug)
namestringDisplay name
typestringNode type: composition, placeholder, redirect, or alias
compositionIdstringLinked composition ID (when type is composition)
compositionDataobjectComposition metadata including editions and locales
localesobjectLocale-specific path overrides
dataobjectCustom node data

getNodes accepts these parameters:

OptionTypeDescription
projectMapIdstringRequired. The project map definition ID.
pathstringFilter nodes starting at this path.
depthnumberHow many levels deep to traverse.
includeDescendantsbooleanInclude all descendant nodes.
compositionIdstringFind the node for a specific composition.

getSubtree returns nodes in a tree structure with parent/child/sibling references. This is ideal for building navigation menus:

const tree = await projectMapClient.getSubtree({ projectMapId: "your-project-map-id", path: "/", }); if (tree) { console.log("Root:", tree.name, tree.path); for (const child of tree.children ?? []) { console.log(" Child:", child.name, child.path); for (const grandchild of child.children ?? []) { console.log(" Grandchild:", grandchild.name, grandchild.path); } } }

The subtree node extends ProjectMapNode with navigation references:

PropertyTypeDescription
childrenProjectMapSubtree[]Child nodes
parentProjectMapSubtreeParent node (references stripped to prevent circular refs)
previousSiblingProjectMapSubtreePrevious sibling node
nextSiblingProjectMapSubtreeNext sibling node

A practical example of building a site navigation from the project map:

import { getProjectMapClient } from "@uniformdev/next-app-router"; type NavItem = { name: string; path: string; children: NavItem[]; }; async function getNavigation(): Promise<NavItem[]> { const projectMapClient = getProjectMapClient({ cache: { type: "force-cache" }, }); const tree = await projectMapClient.getSubtree({ projectMapId: process.env.UNIFORM_PROJECT_MAP_ID!, path: "/", depth: 2, }); if (!tree?.children) return []; return tree.children .filter((node) => node.type === "composition") .map((node) => ({ name: node.name, path: node.path, children: (node.children ?? []) .filter((child) => child.type === "composition") .map((child) => ({ name: child.name, path: child.path, children: [], })), })); }

Use getNodeLocalePath to resolve the correct URL path for a given locale. If a node has locale-specific path overrides, this utility returns the override; otherwise it falls back to the default path:

import { getNodeLocalePath } from "@uniformdev/project-map"; // node.path = "/about" // node.locales = { fr: { path: "/a-propos" }, de: { path: "/ueber-uns" } } const frenchPath = getNodeLocalePath(node, "fr"); // Returns: "/a-propos" const defaultPath = getNodeLocalePath(node, undefined); // Returns: "/about"

Use getRouteAlternateLocalesUrls to generate all locale-specific URLs for a composition route. This is useful for hreflang tags and language switchers:

import { getRouteAlternateLocalesUrls } from "@uniformdev/project-map"; const alternateUrls = getRouteAlternateLocalesUrls(routeResponse); // Returns: { en: "/about", fr: "/a-propos", de: "/ueber-uns" }

Usage in a <head> element:

{alternateUrls && Object.entries(alternateUrls).map(([locale, url]) => ( <link key={locale} rel="alternate" hrefLang={locale} href={url} /> ))}

When using content editions (locale-specific composition variants), use getNodeActiveCompositionEdition to resolve the correct edition for a given locale:

import { getNodeActiveCompositionEdition } from "@uniformdev/project-map"; const activeEdition = getNodeActiveCompositionEdition({ node, targetLocale: "fr", fallbackWhenLocaleNotMatched: true, });
OptionTypeDescription
nodeProjectMapNodeThe node to resolve the edition for
targetLocalestring | undefinedLocale to match. When undefined, returns the default.
fallbackWhenLocaleNotMatchedbooleanWhen true, falls back to default if no edition matches the locale. Default: false.

The Route class provides route pattern matching with dynamic segments (:paramName):

import { Route } from "@uniformdev/project-map"; const route = new Route("/blog/:slug"); // Test if a path matches const result = route.matches("/blog/hello-world"); if (result.match) { console.log(result.pathParams); // { slug: "hello-world" } } // Expand a route with values const expanded = route.expand({ dynamicInputValues: { slug: "my-post" }, }); // Returns: "/blog/my-post"

Combine getNodes with route resolution to generate a sitemap:

import { ProjectMapClient } from "@uniformdev/project-map"; async function generateSitemap(baseUrl: string) { const client = new ProjectMapClient({ apiKey: process.env.UNIFORM_API_KEY!, projectId: process.env.UNIFORM_PROJECT_ID!, }); const { nodes } = await client.getNodes({ projectMapId: process.env.UNIFORM_PROJECT_MAP_ID!, }); const urls = (nodes ?? []) .filter((node) => node.compositionId && !node.path.includes(":")) .map((node) => ({ url: `${baseUrl}${node.path}`, lastModified: new Date().toISOString(), })); return urls; }

For scenarios that require bypassing the API cache (e.g., admin scripts, preview modes), use UncachedProjectMapClient:

import { UncachedProjectMapClient } from "@uniformdev/project-map"; const client = new UncachedProjectMapClient({ apiKey: process.env.UNIFORM_API_KEY!, projectId: process.env.UNIFORM_PROJECT_ID!, });

ExportPackageDescription
ProjectMapClient@uniformdev/project-mapMain project map client
UncachedProjectMapClient@uniformdev/project-mapCache-bypassing client
getNodeLocalePath@uniformdev/project-mapResolve locale-specific path for a node
getRouteAlternateLocalesUrls@uniformdev/project-mapGenerate all locale URLs for a route
getNodeActiveCompositionEdition@uniformdev/project-mapResolve active edition for a locale
Route@uniformdev/project-mapRoute pattern matching and expansion
getProjectMapClient@uniformdev/next-app-routerPre-configured client (server-only, App Router)