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 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();