How to Find Which URLs to Purge From the CDN Cache When a Pattern Is Published
Last updated: June 22, 2026
Problem Statement
When a pattern is published, every composition that uses it is affected — but your CDN doesn't know that. You need to find all compositions referencing the pattern and resolve their URLs so you can purge them from the CDN cache.
Solution
Use the Uniform relationship API (see the API reference) inside a Uniform webhook handler.
1. Handle the publish webhook and check for a pattern
Fetch the published composition with state: 64 and check its pattern flag:
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const payload = (await buffer(req)).toString();
const canvasClient = new CanvasClient({
apiKey: process.env.UNIFORM_API_KEY,
projectId: process.env.UNIFORM_PROJECT_ID,
apiHost: process.env.UNIFORM_API_HOST ?? process.env.UNIFORM_CLI_BASE_URL,
});
const payloadObject = JSON.parse(payload);
// get the composition to know if it is a pattern
const publishedCompositionData = await canvasClient.getCompositionById({
compositionId: payloadObject.id,
state: 64,
});
if (publishedCompositionData?.pattern) {
const affectedPaths = await findAffectedPaths(payloadObject.id);
if (affectedPaths) {
// code for the CDN purge logic
}
}
}
2. Find affected compositions and their paths
Call the relationship API with type=componentPattern to get compositions using the pattern, then resolve each composition to project map paths. Dynamic segments (e.g. :article-title) are replaced with * so a whole section like /blog/* is purged:
const findAffectedPaths = async (patternId: string) => {
const response = await fetch(
"https://uniform.app/api/v1/relationships?ids=" +
patternId +
"&withInstances=true&type=componentPattern&limit=10&offset=0&projectId=411d562d-3340-4b3b-8258-2950a48aab4f",
{
method: "GET",
headers: {
"x-api-key": process.env.UNIFORM_API_KEY,
},
}
);
const data = await response.json();
const allCompositionIds = data.flatMap((item) =>
item.instances.map((wrapper) => wrapper.instance._id)
);
const pjmapClient = new ProjectMapClient({
projectId: process.env.UNIFORM_PROJECT_ID,
apiHost: process.env.UNIFORM_CLI_BASE_URL,
apiKey: process.env.UNIFORM_API_KEY,
});
const allProjectMapNodes = await Promise.all(
allCompositionIds.map(async (compositionId) => {
const { nodes } = await pjmapClient.getNodes({
compositionId: compositionId,
});
return nodes;
})
);
// replace dynamic segments like :article-title with *
const pathsToRevalidate: string[] | undefined = allProjectMapNodes
?.flat()
.map((n) => {
return n?.path?.replace(/\\:.*/g, "*");
});
return pathsToRevalidate;
};
This is sample code and should be optimized. It doesn't include the composition cache purge, only the pattern logic.
3. Purge the returned paths
Pass affectedPaths to your CDN's purge API where the // code for the CDN purge logic comment is.
Troubleshooting
Verify it works: publish a pattern used by a composition and log affectedPaths in the webhook handler — it should list the project map paths of every composition using that pattern.
Empty results from the relationship API: confirm the webhook fired for a pattern (the pattern flag is set) and that projectId in the relationship API URL matches your project.
Dynamic routes not purged: paths like /blog/:article-title are rewritten to /blog/*; make sure your CDN purge call supports wildcard paths.