Knowledge Base/Example code to retrieve composition data with parameters, which may be useful to push into search index

Example code to retrieve composition data with parameters, which may be useful to push into search index

how-toDeveloperSearch and indexing

Uniform combines the component structure and data from external sources via edgehancers, e.g. Uniform code running on the CDN edge. You can use Uniform SDK to pull the final edgehanced compositions JSON as a source to put into the index.

The example code below retrieves all project map nodes, find the compositions for them and combines JSON documents with values from simple parameters types like Text. If you have dynamic project map nodes (such as blog articles or product pages), please update the getSlugsFromCMS function with the actual call to your CMS.

This is a sample code, please update as per your needs
import { RouteClient } from '@uniformdev/canvas'; import { ProjectMapClient } from '@uniformdev/project-map'; import dotenv from 'dotenv'; dotenv.config(); const projectMapId = '40535f31-cdc1-4ac3-bb4d-2a008493b431'; const getRouteClient = () => { const apiKey = process.env.UNIFORM_API_KEY; const edgeApiHost = process.env.UNIFORM_CLI_BASE_EDGE_URL || 'https://uniform.global'; const projectId = process.env.UNIFORM_PROJECT_ID; if (!apiKey) { throw new Error('apiKey is not specified. RouteClient cannot be instantiated: ' + apiKey); } if (!edgeApiHost) throw new Error('edgeApiHost is not specified. RouteClient cannot be instantiated'); if (!projectId) throw new Error('projectId is not specified. RouteClient cannot be instantiated.'); const client = new RouteClient({ apiKey, projectId, edgeApiHost, bypassCache: true, }); return client; }; const projectMapClient = new ProjectMapClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }); const getPaths = async (): Promise<{ path: string }[]> => { const { nodes } = await projectMapClient.getNodes({ projectMapId: projectMapId, }); if (!nodes) { console.warn('No project map nodes found'); return []; } const newNodes = nodes.filter(node => node.compositionId).map(node => ({ path: node.path })); return newNodes; }; const routeClient = getRouteClient(); const getCompositionDataByPath = async (path: string) => { const res = await routeClient.getRoute({ path, projectMapId: projectMapId, state: 0, withComponentIDs: true, }); if (res.type !== 'composition') throw new Error('Requested route is not a composition'); const { composition } = res.compositionApiResponse; return composition; }; const findTextParameterValues = (inputArray: any[]) => { const textValues: any[] = []; function traverseArray(arr: any[]) { arr.forEach(obj => { if (obj.parameters) { // Check if the object has parameters traverseObject(obj.parameters); } }); } function traverseObject(obj: { [x: string]: any }) { for (const key in obj) { if (obj[key].type === 'text') { // If the type is "text", add the value to the result array textValues.push(obj[key].value); } else if (typeof obj[key] === 'object') { // If the property is an object, recursively traverse it traverseObject(obj[key]); } } } traverseArray(inputArray); return textValues.join(', '); }; const retrieveContent = async (composition: any) => { const { parameters, slots } = composition; // content-specific code below const title = parameters?.pageTitle?.value; const pageContent = findTextParameterValues(slots?.pageContent); return { title, pageContent }; }; const composeDataForIndex = async () => { console.log('🗂️ Composing the data to index...'); const pathsToIndex = await getPaths(); const dataToIndex = await Promise.all( pathsToIndex.map(async p => { const compositionDataToAppend = await getCompositionDataByPath(p.path); const processedComposition = await retrieveContent(compositionDataToAppend); return { ...p, ...processedComposition }; }) ); console.log('✅ Data is ready'); return dataToIndex; }; composeDataForIndex().then(data => console.log(data));

You can save it as scripts/rebuild-index.ts and call it on postbuild for example:

"postbuild": "npx ts-node scripts/rebuild-index.ts",
This is a full index rebuild, which may take time for bigger sites. There may be some limitations on how long the NextJS build can take (hosting specific), so you might want to consider trigger the index rebuild outside of NextJS build, especially when adding 100s of pages

When using Uniform dynamic pages, Uniform is not aware of the values which can be used to compose the URL. Therefore the code example below retrieves the slug information from ContentStack to get the full path.

import { RouteClient } from '@uniformdev/canvas'; import { ProjectMapClient } from '@uniformdev/project-map'; import dotenv from 'dotenv'; dotenv.config(); const projectMapId = '40535f31-cdc1-4ac3-bb4d-2a008493b431'; const getRouteClient = () => { const apiKey = process.env.UNIFORM_API_KEY; const edgeApiHost = process.env.UNIFORM_CLI_BASE_EDGE_URL || '<https://uniform.global>'; const projectId = process.env.UNIFORM_PROJECT_ID; if (!apiKey) { throw new Error('apiKey is not specified. RouteClient cannot be instantiated: ' + apiKey); } if (!edgeApiHost) throw new Error('edgeApiHost is not specified. RouteClient cannot be instantiated'); if (!projectId) throw new Error('projectId is not specified. RouteClient cannot be instantiated.'); const client = new RouteClient({ apiKey, projectId, edgeApiHost, bypassCache: true, }); return client; }; const projectMapClient = new ProjectMapClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }); const getSlugsFromCMS = async () => { // here comes the CMS-specific code const url = '<https://cdn.contentstack.io/v3/content_types/blog_post/entries?environment=production&query={}&skip=0&limit=100&include_count=true>'; const apiKey = 'xxx'; const accessToken = 'yyy'; try { const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', api_key: apiKey, access_token: accessToken, }, }); if (!response.ok) { throw new Error(`HTTP error!`); } const data = await response.json(); // Assuming 'entries' is an array within the 'data' object let entriesArray = data.entries; // Now you can use the 'entriesArray' as needed entriesArray = entriesArray.map((entry: any) => { return { slug: entry.url, }; }); return entriesArray; } catch (error: any) { console.error('Error fetching data:', error.message); throw error; } }; const getPaths = async (): Promise<{ path: string }[]> => { const { nodes } = await projectMapClient.getNodes({ projectMapId: projectMapId, }); if (!nodes) { console.warn('No project map nodes found'); return []; } const staticPaths = nodes.filter(node => node.compositionId).map(node => ({ path: node.path })); const newPaths: { path: string }[] = []; const slugsFromCms = await getSlugsFromCMS(); const tokenToReplace = ':article-title'; console.log(slugsFromCms); staticPaths.forEach((node: { path: string }) => { if (node.path.includes(tokenToReplace)) { newPaths.push( ...slugsFromCms.map((slug: { slug: string }) => { return { path: node.path.replace(tokenToReplace, slug.slug), }; }) ); } else { newPaths.push({ path: node.path }); } }); console.log(newPaths); return newPaths; }; const routeClient = getRouteClient(); const getCompositionDataByPath = async (path: string) => { const res = await routeClient.getRoute({ path, projectMapId: projectMapId, state: 0, withComponentIDs: true, }); if (res.type !== 'composition') throw new Error('Requested route is not a composition'); const { composition } = res.compositionApiResponse; return composition; }; const findTextParameterValues = (inputArray: any[]) => { const textValues: any[] = []; function traverseArray(arr: any[]) { arr.forEach(obj => { if (obj.parameters) { // Check if the object has parameters traverseObject(obj.parameters); } }); } function traverseObject(obj: { [x: string]: any }) { for (const key in obj) { if (obj[key].type === 'text') { // If the type is "text", add the value to the result array textValues.push(obj[key].value); } else if (typeof obj[key] === 'object') { // If the property is an object, recursively traverse it traverseObject(obj[key]); } } } traverseArray(inputArray); return textValues.join(', '); }; const retrieveContent = async (composition: any) => { const { parameters, slots } = composition; // content-specific code below const title = parameters?.pageTitle?.value; const pageContent = findTextParameterValues(slots?.pageContent); return { title, pageContent }; }; const composeDataForIndex = async () => { console.log('🗂️ Composing the data to index...'); const pathsToIndex = await getPaths(); const dataToIndex = await Promise.all( pathsToIndex.map(async p => { const compositionDataToAppend = await getCompositionDataByPath(p.path); const processedComposition = await retrieveContent(compositionDataToAppend); return { ...p, ...processedComposition }; }) ); console.log('✅ Data is ready'); console.log(dataToIndex); return dataToIndex; }; composeDataForIndex();
Last modified: January 6, 2025