Back

Scaling 1,000+ Item Syncs to Sitecore XM Cloud with p-limit in Next.js

Thursday, June 4, 2026

The Use Case

When synchronizing 1,000+ provider items into Sitecore XM Cloud, sending too many requests simultaneously can lead to HTTP 429 (Too Many Requests) errors, dropped or throttled API calls, and increased risk of failures in long-running synchronization jobs. Implementing controlled concurrency with tools such as p-limit helps regulate the number of parallel requests, preventing API overload while maintaining consistent throughput. Combined with retry mechanisms, exponential backoff, checkpointing, and batch processing, this approach creates a more resilient integration that can recover from transient failures and continue processing even if a long-running job is interrupted.

This post shows how to use p-limit to reliably sync ~1,000 items from a Next.js app into Sitecore XM Cloud-with batching, retries, and backoff.

Why p-limit?

p-limit gives you a simple way to say:
"Run many async tasks, but only N at a time."

Instead of launching 1,000 requests in parallel, you cap concurrency (e.g., 5-10). That alone dramatically reduces throttling and improves success rates.

But p-limit shines when combined with:

  • Batching - process items in chunks

  • Retries with exponential backoff

  • Small delays between batches


The Target Pattern

Batch → Limit Concurrency → Retry → Pause → Repeat

  • Batch size: 25–100

  • Concurrency: 3–5 (start here)

  • Catch all failed items and retry on failed

  • Delay: 200–500ms between batches


Step 1: Install and set up p-limit

npm install p-limit

import pLimit from "p-limit";

const CONCURRENCY = 5;
const limit = pLimit(CONCURRENCY);

const CONCURRENCY = 5; Defines that no more than 5 async tasks should run simultaneously.
const limit = pLimit(CONCURRENCY); Creates a function (limit) that wraps async operations and ensures that at most 5 are running at any given time.

Step 2: Retry with exponential backoff

Handle transient failures like 429 (rate limit) and 5xx:

const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));

  const retryWithBackoff = async (
  fn: () => Promise<any>,
  retries = 3,
  delay = 500
) => {
  try {
    return await fn();
  } catch (err: any) {
    const status = err?.response?.status;

    if (retries > 0 && (status === 429 || status >= 500)) {
      await sleep(delay);
      return retryWithBackoff(fn, retries - 1, delay * 2);
    }

    throw err;
  }
}; 

It tries to execute an async function (fn). If the operation fails because the server is overloaded or temporarily unavailable, it waits for some time and tries again. Each retry waits longer than the previous one. It handles Rate limiting (HTTP 429), Temporary server failures (HTTP 500, 502, 503, 504, etc.) and Unstable network conditions.


Step 3: Wrap your Sitecore call

Create a single function responsible for syncing one item:

const syncToSitecore = async (item: any) => {
  return retryWithBackoff(async () => {
    const res = await fetch("/api/sitecore-sync", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(item),
    });

    if (!res.ok) {
      const error: any = new Error("Request failed");
      error.response = { status: res.status };
      throw error;
    }

    return res.json();
  });
};
 

syncToSitecore(item)
POST /api/sitecore-sync
Success (200-299)
Return JSON response
Failure
Throw error with status
retryWithBackoff()
Retry if 429 or 5xx

Step 4: Add batching + p-limit

Now combine batching with controlled concurrency:

const BATCH_SIZE = 50;

const processItems = async (items: any[]) => {
  for (let i = 0; i < items.length; i += BATCH_SIZE) {
    const batch = items.slice(i, i + BATCH_SIZE);

    console.log(`Processing batch ${i / BATCH_SIZE + 1}`);

    const tasks = batch.map(item =>
      limit(() => syncToSitecore(item))
    );

    await Promise.all(tasks);

    await sleep(300); // small pause to avoid throttling
  }
};

This code processes a large list of items and sends them to Sitecore in batches, while also limiting concurrency and adding retries. It

  • Splits the items into groups of 50.

  • Processes one group at a time.

  • Within each group, only 5 requests run simultaneously (because of your earlier p-limit(5)).

  • Retries failed requests using retryWithBackoff().

  • Waits 300 ms between batches.

200 Items
Batch 1 (1–50)
Batch 2 (51–100)
Batch 3 (101–150)
Batch 4 (151–200)

Step 5: Add failure tracking (don’t skip this)

const failedItems: any[] = [];
const safeSync = async (item: any) => {
  try {
    return await syncToSitecore(item);
  } catch (err) {
    failedItems.push(item);
  }
};
Then use:
const tasks = batch.map(item =>
  limit(() => safeSync(item))
);

This lets you:

  • retry later

  • log errors

  • avoid silent data loss


Optional: Adaptive throttling

let dynamicDelay = 300;

if (res.status === 429) {
  dynamicDelay += 200;
}
Then:
await sleep(dynamicDelay);

Where to run this code

You can run this in a .tsx component (e.g., useEffect or a button handler), but for 1,000+ items:

Better options

  • Next.js API route (/api/sync)

  • Background worker (Node, Azure Function, etc.)

Why

  • avoids browser limits

  • prevents UI blocking

  • more reliable for long-running jobs


What NOT to do

❌ Don't do this
await Promise.all(items.map(syncToSitecore));

This causes:

  • request spikes

  • throttling (429)

  • unstable syncs


Tuning guidelines

Start conservative:

  • Concurrency: 5

  • Batch size: 50

  • Delay: 300ms

Then adjust:

  • Increase concurrency if stable

  • Reduce if you see 429s

  • Monitor response times


Final Takeaway

In conclusion, synchronizing large datasets into Sitecore XM Cloud is most effective when controlled concurrency is combined with the platform’s built-in scalability and content delivery capabilities. While p-limit serves as the essential concurrency guardrail that prevents API overload, it becomes truly production-ready when paired with batching, retry mechanisms, and request pacing, helping mitigate issues such as rate limiting, dropped requests, and long-running process failures. Sitecore XM Cloud’s API-first architecture, scalable SaaS infrastructure, Experience Edge, and content management APIs work seamlessly alongside Next.js features such as Static Site Generation (SSG), Incremental Static Regeneration (ISR), intelligent caching, and On-Demand Revalidation to ensure content updates are processed efficiently and delivered quickly to end users. Together, these capabilities provide high success rates, predictable performance, and resilience under load, enabling organizations to reliably synchronize thousands of content items while maintaining fast, scalable, and always-fresh digital experiences.