Skip to content
Dashboard

Virtual waiting rooms for product drops

A limited-stock-keeping unit (SKU) goes live at noon, and the first minute determines whether the drop reads as a sellout or an outage. A virtual waiting room is one control that separates the two, and most drops never need it. What decides is the ratio of demand to inventory, the release mechanic, and the slowest dependency behind checkout. This guide covers what a waiting room does, when a drop earns one, and what it costs to buy or build.

Key takeaways:

  • A virtual waiting room exists to stop four specific failures: storefront collapse, inventory oversell, payment gateway rejection, and database connection pool exhaustion.

  • Most drops don't need one, because Vercel Functions burst to 1,000 concurrent executions per 10 seconds per region and the CDN collapses concurrent requests for one uncached path into a single invocation.

  • A queue earns its complexity on a single hot SKU at a timed release, where demand lands on one inventory row and one checkout path within seconds.

  • Cloudflare Waiting Room requires Cloudflare's CDN in front of the site and puts randomized entry behind Enterprise, while Queue-it publishes Essentials from $1,499 per event, so buying wins whenever the drop calendar covers the fee.

  • A queue meters the front door and moves the bottleneck one step inward, so checkout, inventory, and payments each still need their own limit behind it.

Copy link to headingWhat is a virtual waiting room?

A virtual waiting room is a control that intercepts requests ahead of your origin, holds surplus visitors on a page served from separate infrastructure, and releases them to the storefront at a configured rate. The admission rate is the entire product. The countdown, the position number, and the estimated wait exist to make the delay legible to a shopper who would otherwise be watching a spinner.

A queue protects the transaction path rather than the home page. Catalog pages cache. Carts, inventory decrements, and payment authorizations do not, and each has a ceiling set by something you don't control.

Copy link to headingHow a virtual waiting room differs from autoscaling and rate limiting

The distinction that matters is what each control does with the visitor it can't serve right now. The last column is the gap that sends teams looking for the next control:

Control

What it does with excess demand

What it leaves unsolved

Autoscaling

Adds capacity once the surge is detected

The seconds between arrival and capacity

CDN caching and request collapsing

Serves or collapses repeated reads

Writes: cart, checkout, and inventory decrement

Rate limiting

Rejects requests above a threshold

Fairness, because a rejected shopper gets an error rather than a position

Virtual waiting room

Defers visitors and admits them in order at a set rate

Everything behind the door once they are admitted

A load balancer sits outside this table: it forwards every request and holds none, which is why fronting a drop with more instances relocates the failure without preventing it.

Copy link to headingWhy a waiting room matters on drop day

A queue earns its complexity when it prevents one of four failures. Each happens somewhere other than the page the shopper is watching:

  • Storefront collapse under simultaneous load: Ticketing platform Shotgun sees traffic jump 200x in minutes when tickets go live. With a queue in front, the origin only sees the admitted share.

  • Inventory oversell from race conditions: Two requests read a stock count of one, both decrement, and the row goes negative. SeatGeek's DynamoDB waiting room removes the race by minting access tokens equal to the ticket count before the sale opens, so holding a token is the reservation.

  • Payment gateway rejection: Stripe rate limits allow 100 requests per second globally in live mode and 25 per second on an individual endpoint, and Stripe names a sudden flash sale as a cause of rate limiting.

  • Database connection pool exhaustion: During a March 2026 Buttondown outage, connections were saturated so thoroughly that the team couldn't reach the database to kill the offending queries, turning a 7-minute incident into a 13-minute one.

Three of those four are write-path failures, and adding compute fixes none of them. That is the argument for a queue, and also the reason most drops don't qualify, because a drop with no single contended write gives the queue nothing to protect.

Copy link to headingWhen a product drop needs a waiting room, and when it does not

The platform mechanics are why most drops clear without one. The Functions router under Fluid compute fills in-flight instances before it spawns new ones. On a cache miss, the CDN applies request collapsing so concurrent requests for the same uncached path wait on a single invocation, while cache hits never reach compute at all. Our network peaked at 518,027 requests per second across Black Friday and Cyber Monday 2025.

The ceiling that matters on drop day is the burst rate. Vercel initially scales by up to 1,000 concurrent executions per 10 seconds, per region. Large bursts can take several minutes to scale, so test the first-minute behavior and monitor for FUNCTION_THROTTLED errors.

This table reads against the drop you're planning, not against your annual peak:

Signal

Queue warranted

Reason

Demand-to-inventory ratio above 100:1 on one SKU

Yes

Contention lands on a single inventory row

Timed first-come-first-served release at an announced minute

Yes

Every arrival falls inside the first burst window

Checkout would exceed 25 requests per second at the gateway

Yes

The ceiling belongs to a third party

Inventory in a system with a fixed connection pool

Yes

Pool exhaustion locks out recovery

Raffle or registration mechanic with a time-windowed entry

No

Entry is stateless and cacheable

Deep inventory, demand at or below supply, no hot SKU

No

Caching and connection pooling absorb it

Timezone-rolling global sale

No

Regional sharding spreads the peak

One qualifying row is enough, because these failures compound instead of averaging out. Once a drop lands on a yes, the next decision is whether to buy the queue or build it.

Copy link to headingQueue-it, Cloudflare Waiting Room, or a queue you build

Two managed products already solve the parts that take weeks to rebuild: fairness randomization at sale start, pre-queues with countdowns, and abuse filtering at the perimeter. Price both before writing any code.

Cloudflare Waiting Room needs Cloudflare's CDN in front of the site through a proxied DNS record or load balancer, which makes it an architecture decision before it is a queueing one. Queue-it's pre-queue gathers early visitors behind a countdown and randomizes their positions when the timer hits zero, which is what stops a fast script from beating a shopper who arrived on time. Its invite-only mode goes further, validating signed tokens before a visitor reaches the site at all.

The useful comparison is on the constraint that binds your drop, not on feature count:

Dimension

Cloudflare Waiting Room

Queue-it

Custom on Vercel

Queue algorithms

First-in-first-out (FIFO) on Business, plus Random, Passthrough, and Reject on the Enterprise-only Advanced add-on

FIFO with pre-queue randomization, plus invite-only

Yours to define, FIFO in the reference implementation

Scheduled event or pre-queue

Advanced add-on, Enterprise only

Included

Not in the reference implementation

Abuse filtering

Turnstile on the Advanced add-on

Traffic access rules and signed invite tokens

Vercel WAF ahead of the queue, BotID after admission

Cloudflare CDN required

Yes

No

No

Effect on Vercel Bot Protection

Degraded, because a reverse proxy sits in front

None, no proxy in the request path

None

Entry cost

Business plan at $200 per month billed annually

Essentials from $1,499 per event

Upstash Redis from $10 per month, plus engineering time

Operational burden

Managed from the dashboard

Managed service with connector setup

You own queue state, cookies, and expiry

Randomized entry is the line item to check first, since Cloudflare puts it on an Enterprise-only add-on while Queue-it includes it at the published event price. Buy when the drop calendar covers the fee and the sale needs that randomization or invite-only entry, which are the expensive parts to build and the parts shoppers judge you on. Build when the holding page has to carry your brand, when the queue must read your own inventory state to decide admission, or when fronting Vercel with another CDN would cost you controls you already depend on. That last one costs something concrete. The Bot Protection managed ruleset doesn't work behind a reverse proxy, because the proxy masks the signals detection relies on and rotating exit IPs force repeated challenges.

Copy link to headingHow to build a virtual waiting room on Vercel in 5 steps

The queue itself is a counter, a token, and a sorted set. Drops go wrong one layer down, where a queue that worked last season stops being called at all after a framework upgrade. Next.js 16 renamed middleware.ts to proxy.ts and the export from middleware to proxy, and the proxy function runs on Node.js only. Run npx @next/codemod@canary middleware-to-proxy . to migrate an existing queue before the next drop. State goes in Redis rather than Global Config, whose writes take up to 10 seconds to propagate globally, which is correct for feature flags and wrong for a counter read on every request.

Copy link to headingStep 1: Make the admission counter atomic

The counter is INCR plus EXPIRE, which makes it a fixed window and inherits that algorithm's weakness at the boundary of each interval. The two have to run inside one script. Run them as separate commands and a crash between them leaves a key with no expiry, which never resets the window and stalls the queue permanently.

Pass the interval as an argument so the window is configurable without a deploy:

local current = redis.call('incr', KEYS[1])
if current == 1 then
redis.call('expire', KEYS[1], ARGV[1])
end
return current

Redis scripts execute atomically and block all other server activity while they run, which makes the read-modify-write safe under concurrent admission attempts.

Copy link to headingStep 2: Issue a signed admission token

When the counter comes back under the limit, mint a short-lived signed token, either a JSON Web Token (JWT) or an encrypted httpOnly cookie carrying the queue position and the issued-at and expiry timestamps. Store nothing in Redis for admitted visitors. The signature is the proof.

The failure to avoid is a token that the origin validates by calling back to the queue service on every request, which makes storefront uptime depend on queue uptime and inverts the reason the queue exists. Use an opaque server-backed token when admission must be revocable, single-use, bound to server-side state, or too large for a cookie

Copy link to headingStep 3: Track queue position with a sorted set

Visitors who miss the cut go into a Redis sorted set through ZADD queue_name timestamp visitor_id, and ZRANK returns the same position from every proxy instance. Send that position and the current serving number to the browser so the holding page can estimate a wait.

The Vercel Labs reference implementation delays allocation until the first poll of its status endpoint rather than the first miss on a protected route. Bots and bounces never cost a Redis write, and the trade is a lenient arrival-time fairness, which is the right default for retail and the wrong one where arrival order is the promise.

Copy link to headingStep 4: Handle refreshes without re-queuing

On every request, the proxy checks for a valid admission cookie first and passes the request through on a good signature without touching the counter. A missing or expired cookie sends the visitor to a position lookup in the sorted set before anything is reissued. Skip this ordering and a shopper who refreshes during checkout loses their place, which produces the support volume a queue was supposed to prevent.

Copy link to headingStep 5: Expire abandoned sessions

Store each position's expiry in a second sorted set, remove stale members from both sets when advancing the serving number, and count only tokens whose expiry is still in the future. If abandonments outpace completions and nothing expires them, the queue stalls, holding capacity it refuses to use.

Fail open favors availability but can oversubscribe scarce inventory. Use it for launches where temporary over-admission is acceptable; use fail closed, or a separately enforced reservation limit, when inventory is a hard ceiling.

Copy link to headingWhat to test before the waiting room goes live

A queue has several independent ways to fail, and most surface only under concurrent load. A preview deployment is the place to rehearse, and the terms are worth reading before you generate any of that load. Vercel's load-testing policy permits it on Enterprise plans and requires prior notice, and an unannounced run risks breaching fair use and getting your source IPs blocked.

Copy link to headingVerify Redis atomicity under concurrency

Point a constant-arrival-rate scenario at the admission endpoint, then assert that admitted requests never exceed the configured limit. After each run, check the counter key's time to live (TTL). A counter with no expiry means the script wrapping isn't in place, and it will pass every sequential test you write.

Copy link to headingCheck the holding page's cache state

The x-vercel-cache header reports MISS on the first request, HIT on the second, and STALE on the first request after the revalidation window. That header alone doesn't prove the page is static or using Incremental Static Regeneration (ISR). Confirm the cache reason in Runtime Logs as well. A holding page that misses on every request is a second origin under load.

Next.js 16.3 moved this baseline for anyone running Cache Components. A page you didn't prerender now serves a loading shell on its first visit and upgrades in the background, so prerender the holding page at build time instead of reading first-visit behavior as proof it's cached.

Copy link to headingSet the admission rate from the slowest dependency

Derive the rate from whatever is slowest behind the door, which is usually the payment gateway rather than your own compute. If the queue would push more checkouts per second than that ceiling allows, lower the rate or add a second queue at the checkout step. Stripe discourages sizing this against a sandbox, whose limits are lower than live mode, so the test fails in ways production wouldn't.

Token lifetime sets the other half of the figure. Admitting 100 shoppers a minute on a 10-minute token leaves up to 1,000 holding access at once, which is the number checkout actually has to survive.

Copy link to headingConfirm the perimeter controls behave as configured

Vercel's web application firewall (WAF) tracks rate-limit counters per region, so traffic matching one key across several regions can exceed the limit configured for any single region. The budget belongs per region. Token Bucket counting is also Enterprise-only. That leaves a Pro team's WAF limit on Fixed Window, which lets bursts leak through at window boundaries exactly when a timed drop opens. Testing BotID takes a fetch from inside the app, because a curl request or a direct visit to a protected route is blocked in production by design.

Copy link to headingHow Vercel supports virtual waiting rooms for high-demand drops

How much of the surrounding system you assemble first decides whether the queue guards a real bottleneck or one the platform would have absorbed on its own.

Copy link to headingA proxy layer that runs before the cache

A queue implemented inside the application has already paid for the request it meant to defer. Routing Middleware executes ahead of the cache and your routes, so a held visitor never reaches a function that touches inventory. Priced on the fluid compute model, a proxy that checks a cookie signature and returns costs close to nothing per request at drop volume.

Copy link to headingProof that the platform absorbed the read traffic

Deciding a drop doesn't need a queue is only safe if you can audit that call afterward. Runtime Logs record a Request collapsed cache reason on every request that waited on an in-flight generation instead of triggering a second invocation, so origin shielding either shows up in last season's logs or it never happened. That reads against the cache status before you commit to running the next drop unqueued.

Copy link to headingAbuse controls on both sides of the door

A queue without bot controls hands positions to scripts, the failure that makes a fair-looking queue unfair. Bot Protection and Vercel WAF rules belong ahead of the waiting room, with BotID on expensive routes such as checkout after admission. Vercel Firewall rules propagate globally within 300 milliseconds with no redeploy, which means a rule can ship mid-drop. A queue is one layer of a storefront's wider attack surface, and it guards exactly one of them.

Copy link to headingLog retention that outlives the drop

Post-mortems happen the week after a drop, by which point Pro's one day of runtime log retention has discarded the window you need. Observability Plus extends retention to 30 days on Pro and Enterprise. Retention isn't retroactive, so it has to be on before the drop rather than after it, and the first drop teaches the most.

Copy link to headingShip your next drop behind a queue you control

The Eras Tour on-sale ran behind a verified-fan queue in November 2022, generated 3.5 billion system requests at four times the previous peak, and still left an estimated 15% of interactions across the site failing. The queue did its job. Fans waited, got admitted, and met a checkout that couldn't absorb the rate the queue fed it. A waiting room meters the front door and relocates the bottleneck one step inward, where failure costs more than a sold-out page does. That makes the queue the least interesting control in the system, and the one teams spend the most time on.

Behind that door, every layer needs its own limit. Checkout gets Stripe idempotency keys so a retried request returns the first result rather than a second charge, and inventory gets atomic decrements at the database. Here's what the platform covers, so your team can spend its time on the layers a queue can't reach:

  • Routing Middleware on fluid compute: Queue logic runs ahead of the cache, billed on compute actually consumed. Deferring a visitor costs a cookie check rather than an inventory read.

  • CDN request collapsing: Origin shielding on cache misses, logged as a Request collapsed cache reason you can audit after the drop instead of assuming.

  • Concurrency scaling: Functions scale to 30,000 on Pro or 100,000 on Enterprise without preprovisioning, which leaves the queue guarding contended writes only.

  • Vercel WAF and BotID: Perimeter filtering before a position is allocated, invisible checks on the routes that spend money, and rule changes live worldwide in 300 milliseconds with no deploy.

  • Observability Plus: Thirty days of runtime log retention, so the post-mortem runs against the traffic that actually arrived.

Start a new project to put a queue in front of your next release, or browse the templates for a commerce starting point already wired for production traffic.

Copy link to headingFrequently asked questions about virtual waiting rooms

Copy link to headingDoes Cloudflare Waiting Room work in front of a Vercel deployment?

Yes, on one condition. Cloudflare's CDN has to proxy your domain for the feature to run at all. Vercel auto-enables Verified Proxy for Cloudflare, so real client IPs still reach your rate-limit keys through CF-Connecting-IP. Bot Protection accuracy is what you give up.

Copy link to headingDoes Next.js 16 break a waiting room built on middleware?

Not immediately, since middleware.ts still works while deprecated. The runtime is the real change. On Vercel that file convention defaults to the Edge Runtime, and proxy.ts drops it, so any Edge-specific dependency in the queue needs an audit before the codemod runs.

Copy link to headingHow many concurrent shoppers can a site handle before it needs a waiting room?

The answer comes from inventory rather than traffic. Expected buyers divided by the units you're releasing gives the ratio that matters. On deep stock the platform absorbs the arrival curve, and on a single hot SKU the checkout ceiling binds long before compute does.

Copy link to headingHow do you stop bots from taking slots in a custom waiting room?

Filtering belongs ahead of the admission check, since a bot that reaches the counter has already taken a position. BotID Basic is free on every plan, while Deep Analysis bills $1 per 1,000 checkBotId() calls on Pro, so the metered check goes on checkout and the status poll stays on Basic.

Ready to deploy?