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.

Resources