Configure Next.js App Router Entry Relationships Revalidation and Page Cache clearing
Last updated: March 10, 2026
This guide explains how to implement automatic page revalidation in Next.js when Uniform entries are published based on relationships with other entities.
The Problem
When using Next.js with Incremental Static Regeneration (ISR) or static site generation, pages are cached for performance. When content is updated in Uniform CMS, these cached pages need to be invalidated so visitors see the latest content.
The challenge is that entries (content items) are not directly tied to pages. An entry might be:
Used directly in a composition (page)
Referenced by another entry, which is then used in a composition
Part of a component pattern that's used across multiple compositions
Nested several levels deep in your content structure
When you publish an entry, you need to find and revalidate all pages that display that entry's content, regardless of how deeply nested the reference is.
For revalidation on fetches using tag:
Why a Custom Solution is Needed
Uniform's built-in page revalidation does not include handling of entry published events and the pages potentially referenced by that entry. This is because there are many ways in which entry content might be represented in a page making it impossible to implement an exhaustive solution without knowledge of the underlying business logic.
This solution provides a starting-point for the application to implement an entry revalidation strategy. It includes a recursive traversal algorithm to find all affected pages, handling circular references, component patterns, entry patterns, and API rate limits.
This recursive traversal must also handle:
Circular references: Entry A references Entry B, which references Entry A
Component patterns: Reusable components that appear on multiple pages
Entry patterns: Reusable entry structures
API rate limits: Avoiding excessive API calls
Dependencies
Download the webhook route code.
Add the following packages to your project:
npm install svix @uniformdev/canvas @uniformdev/project-map
# or
pnpm add svix @uniformdev/canvas @uniformdev/project-map
Package | Purpose |
| Webhook signature verification (Uniform uses Svix for webhooks) |
| Provides |
| Provides |
Environment Variables
Configure the following environment variables:
# Required: Your Uniform API key
UNIFORM_API_KEY=your-api-key
# Required: Uniform API host
# Use "https://uniform.app" for US region
# Use "https://eu.uniform.app" for EU region
UNIFORM_API_HOST=https://uniform.app
# Recommended: Webhook secret for signature verification
# Get this from your Uniform webhook configuration
UNIFORM_WEBHOOK_SECRET=your-webhook-secretSecurity Note: If UNIFORM_WEBHOOK_SECRET is not set, the webhook will skip signature verification. This is acceptable for local development but not recommended for production.
Step by Step Implementation
Install the required dependencies
Save the code to
app/api/revalidate-entry/route.tsin your Next.js project.Add the webhook to your Uniform project
Set up your environment variables in a
.envfile or using your preferred method.Test the webhook locally and verify that it correctly identifies affected pages.
Deploy your application to a production environment and ensure that the webhook is properly configured in Uniform CMS.
Configuration
MAX_RECURSION_DEPTH
The MAX_RECURSION_DEPTH configuration controls how many levels deep the relationship traversal goes for each entity type:
const MAX_RECURSION_DEPTH: Record<EntityType, number> = {
entry: 1,
entryPattern: 0,
componentPattern: 1,
compositionPattern: 1,
};Understanding Depth Values
A depth is the number of levels from the original entry.
A depth of 0 means: don't follow any relationships for this type. A depth of 1 means: fetch direct relationships only. A depth of 2 means: fetch relationships, then fetch relationships of those results, etc.
Choosing Values
Entity Type | Recommended | When to Increase |
| 1-2 | If entries reference other entries that reference compositions |
| 0-1 | If entry patterns are used in deeply nested structures |
| 1 | If component patterns contain other component patterns |
| 1 | If composition patterns are nested |
Guidelines:
Start conservative: Begin with low values (0-1) and increase if pages aren't being revalidated
Monitor API usage: Higher depths mean more API calls per webhook
Consider your content model: Flat structures need lower depths; deeply nested structures need higher
Check logs: The webhook logs the
maxDepthReachedstat to help you tune these values
MAX_TOTAL_API_CALLS
const MAX_TOTAL_API_CALLS = 10;This is a safety limit to prevent runaway API usage. If your content model requires more traversal, increase this value. The webhook will warn in logs when this limit is reached.
Code Walkthrough
The implementation is in app/api/revalidate-entry/route.ts. Here's a section-by-section explanation:
1. Webhook Payload Handling
export async function POST(request: NextRequest) {
// Parse and verify webhook payload
let payload: WebhookPayload;
if (process.env.UNIFORM_WEBHOOK_SECRET) {
// Verify signature using Svix
const svix = new Webhook(process.env.UNIFORM_WEBHOOK_SECRET);
payload = svix.verify(rawBody, {
'svix-id': svixId,
'svix-timestamp': svixTimestamp,
'svix-signature': svixSignature,
}) as WebhookPayload;
} else {
// Development mode: skip verification
payload = await request.json();
}What it does: Receives the webhook from Uniform and verifies its authenticity using Svix signature verification.
When to customise: Modify the WebhookPayload interface if Uniform's webhook payload structure changes.
2. Event Type Filtering
const HANDLED_EVENT_TYPES = ['entry.published'];
if (!HANDLED_EVENT_TYPES.includes(payload.eventType)) {
return NextResponse.json({
message: 'Ignored event type',
eventType: payload.eventType,
}, { status: 200 });
}What it does: Only processes entry.published events; ignores others.
When to customise: Add other event types like entry.unpublished if you need to handle them differently.
3. API Client Initialisation
const relationshipClient = new RelationshipClient({
apiHost: process.env.UNIFORM_API_HOST!,
apiKey: process.env.UNIFORM_API_KEY!,
projectId,
});
const projectMapClient = new ProjectMapClient({
apiHost: process.env.UNIFORM_API_HOST!,
apiKey: process.env.UNIFORM_API_KEY!,
projectId,
});What it does: Creates Uniform SDK clients for querying relationships and resolving paths.
When to customise: If you have multiple Uniform projects, you might need to handle different API keys.
4. BFS (Breadth-First Search) Traversal
async function findAllAffectedPathsBFS(
startEntityId: string,
startEntityType: EntityType,
relationshipClient: RelationshipClient,
projectMapClient: ProjectMapClient
)What it does: Traverses the relationship graph level-by-level to find all compositions that use the published entry.
Key features:
Visited Set: Prevents infinite loops from circular references
Queue-based BFS: Processes entities level-by-level for efficient batching
Type-based grouping: Batches API calls by entity type
Depth tracking: Respects
MAX_RECURSION_DEPTHper entity type
When to customise: Modify if you need different traversal logic or want to handle additional entity types.
5. Relationship Processing
if (instanceType === 'composition') {
// Found a composition - collect for path resolution
compositionIds.add(instanceId);
visited.add(nextKey);
} else if (instanceType === 'entry') {
// Found an entry - add to queue for further exploration
const nextType: EntityType = instance.pattern ? 'entryPattern' : 'entry';
queue.push([instanceId, nextType, currentDepth + 1]);
} else if (instanceType === 'component' && instance.pattern) {
// Found a component pattern - need to find compositions using this pattern
queue.push([instanceId, 'componentPattern', currentDepth + 1]);
}What it does: Determines what to do with each discovered relationship:
Compositions: Collect their IDs for path resolution
Entries: Add to queue for further traversal (distinguishing patterns from regular entries)
Component patterns: Add to queue since they may be used in multiple compositions
Non-pattern components: Skip (they're part of the composition already found)
When to customise: If your content model uses relationships differently.
6. Path Resolution
async function resolveCompositionPaths(
projectMapClient: ProjectMapClient,
compositionIds: string[],
incrementApiCall: () => void
): Promise<string[]>What it does: Converts composition UUIDs to URL paths using the Project Map API.
When to customise: If you have custom routing logic or need to transform paths.
7. Revalidation
for (const path of paths) {
try {
revalidatePath(path);
revalidatedPaths.push(path);
} catch (err) {
console.error(`Failed to revalidate path: ${path}`, err);
failedPaths.push(path);
}
}What it does: Calls Next.js revalidatePath() for each discovered path.
When to customise:
Use
revalidateTag()instead if you're using tag-based revalidationAdd additional logic like cache purging for CDNs
Customisation Guide
Adding Support for Other Event Types
To handle entry.unpublished or composition events:
const HANDLED_EVENT_TYPES = ['entry.published', 'entry.unpublished'];
// Then in the handler:
if (payload.eventType === 'entry.unpublished') {
// Custom logic for unpublished entries
}Handling Multiple Locales
If your site has localised paths:
const locales = ['en', 'fr', 'de'];
for (const path of paths) {
for (const locale of locales) {
revalidatePath(`/${locale}${path}`);
}
}Webhook Setup in Uniform
Go to your Uniform project settings
Navigate to Webhooks
Create a new webhook with:
URL:
https://your-domain.com/api/revalidate-entryEvents: Select
entry.published(and any other events you want to handle)Secret: Copy the signing secret to your
UNIFORM_WEBHOOK_SECRETenvironment variable
Troubleshooting
Pages Not Revalidating
Check logs for the webhook response—it shows which paths were revalidated
Verify the
maxDepthReachedstat isn't hitting yourMAX_RECURSION_DEPTHlimitsEnsure the composition is in the Project Map (orphaned compositions won't have paths)
Too Many API Calls
Lower
MAX_RECURSION_DEPTHvaluesCheck if your content model has circular references creating loops
Review the
apiCallsstat in webhook responses
Signature Verification Failing
Ensure
UNIFORM_WEBHOOK_SECRETmatches the secret in Uniform's webhook configurationCheck that the webhook headers (
svix-id,svix-timestamp,svix-signature) are being forwarded correctly
Response Format
The webhook returns a JSON response with revalidation details:
{
"success": true,
"entityId": "abc-123",
"entityName": "My Entry",
"revalidatedPaths": ["/blog/my-post", "/about"],
"stats": {
"apiCalls": 3,
"entitiesChecked": 5,
"compositionsFound": 2,
"maxDepthReached": 1,
"pathsRevalidated": 2,
"durationMs": 245
}
}Use these stats to monitor and tune your configuration.