Blog

Prebid.js vs Prebid Server: Which to Run, and When to Run Both

Where each auction runs, what the trade costs in latency and match rate, which demand belongs on which side, and how to migrate to Prebid Server safely.

19 min read By the Floxis engineering team

Prebid.js runs the auction in the browser where the cookies are; Prebid Server runs it in your cluster where the user arrives a stranger

The Prebid documentation is exhaustive about both products and silent on the only question most publishers actually have. It will teach you every s2sConfig attribute and every cookie sync redirect, and it will never tell you which one to run — Prebid.org is a consortium, and a consortium does not pick winners between its own projects.

We operate both — Prebid.js infrastructure and Prebid Server infrastructure — so we will pick. The short version: client-side is where identity and demand density live, server-side is where scale and page performance live, and almost every serious deployment ends up running both, split by a rule you can write down. The rest of this article is the mechanism behind that sentence, the conditions under which each answer is correct, and the migration order that loses the least revenue. Where the Prebid docs state a default or a number, we cite it rather than recall it.

Table of Contents

What actually changes when the auction moves?

One thing changes, and everything else follows from it: who makes the network calls to demand.

With Prebid.js alone, the user’s browser is the auction house. Every bid adapter in your build fires its own request from the page, each bidder answers directly to the device that will display the ad, and Prebid.js passes the winners to your ad server. With Prebid Server, the page sends one request to a server you (or a host) operate, and the fan-out happens in a data center: per the docs, the server validates and enhances the request, resolves stored request configurations, enforces privacy regulations, calls the server-side adapters, converts currencies, quantizes bids for targeting, and caches VAST when video needs it.

Every trade-off in this article is a consequence of that relocation:

Dimension Client-side (Prebid.js) Server-side (Prebid Server)
Network calls to demand One per bidder, from the user’s device One from the page; fan-out on servers
Bidder’s view of the user Its own cookie, read on its own domain Whatever ID the sync or eids carried
Page cost JS weight + CPU + concurrent requests One request; near-zero added page work
Who pays for compute Your users’ browsers Your servers (or your host’s)
Visibility Every call inspectable in devtools One opaque POST; truth lives in server logs
Where it can run Web pages only Web, mobile app, AMP, CTV, DOOH, audio

That last row comes first in any honest weighing: for mobile app, AMP, and server-side ad insertion channels like CTV and audio, the docs list Prebid Server as the mechanism, full stop. “Which should I run” is only a live question for web inventory. Everywhere else, the answer was made for you.

The browser cost of client-side Prebid.js

Client-side header bidding spends a budget the publisher never sees on an invoice: the user’s device and the user’s patience.

Each additional client-side bidder adds adapter code to your build, one more concurrent network request at auction time, and one more response parsed on the main thread that is also trying to render your content. The Prebid intro docs are unusually direct about the consequence: client-side implementations typically run 5–15 demand partners, the documented latency remedy is to minimize the bidders allowed into the auction, and server-side setups “will often have more” partners because the processing is faster. The pressure is sharpest on slow devices and networks — the traffic where you were already earning the least — and the timeout docs note that when Prebid.js cannot get attention from a busy browser, timeouts fire more often. Heavy pages don’t just load slower; they lose bids.

There is an operational cost too. Prebid.js is a build you assemble — adapters, modules, version — and ship inside your pages, with upgrades recommended at least every six months. Every change to your bidder mix is a client-side release, and coordinating wrapper releases across a portfolio is a real tax.

What client-side buys for all that: every bidder sees the user through its own eyes. That is the single biggest reason not to move everything to the server.

The identity cost of server-side Prebid

When a client-side adapter calls its bidder, the request arrives at the bidder’s own domain carrying the bidder’s own cookie — the best identifier it will ever have. When Prebid Server calls the same bidder from a data center, no browser is involved and no cookie rides along. The only identity available is what the server can attach, and attaching it is the machinery the Prebid Server user sync docs describe.

The mechanism, straight from those docs: Prebid Server consolidates bidder IDs into a uids cookie in its own host domain. When s2sConfig is set, Prebid.js calls the server’s /cookie_sync endpoint listing the participating server-side bidders; the server checks which IDs are missing or stale and responds with sync URLs; Prebid.js fires a pixel for each; the bidder’s endpoint reads its own cookie and redirects back to the server’s /setuid endpoint, which writes that bidder’s ID into uids for next time.

Count the failure points. The pixels must have run on some earlier page view, each redirect must complete, the uids cookie must survive in the browser, and the bidder’s own third-party cookie must be readable when the pixel fires. Every step is attritional, which is why a bidder’s server-side match rate is structurally lower than its client-side recognition rate: the client path reads the primary ID directly, the server path reads a copy that had to be ferried across domains in advance and kept fresh.

Lower match rate is not cosmetic. A DSP that cannot match the user to frequency caps, retargeting lists, or segments bids less often and bids lower. Move an identity-hungry bidder server-side and its bid rate and average CPM will sag though nothing else changed. This is the classic migration regression — mechanical, not accidental — and you should expect to observe it rather than hope to avoid it.

What keeps identity alive server-side

The degradation is real but not unmanaged. Four documented mechanisms pull match rates back up.

Cooperative syncing. Prebid Server can sync bidders beyond the ones on the current page — the coopSync flag in s2sConfig, default true. The docs frame it exactly as a match-rate lever: publishers help each other improve match rates by allowing it.

Sync budget controls. userSyncLimit caps how many sync URLs fire per request; syncTimeout (default 1000 ms) bounds each. Syncing competes with your page for network, and the timeout docs advise delaying user sync long enough for the first auction to run, then letting syncs fill the table for later auctions.

User ID modules. The Prebid.js User ID module establishes IDs client-side — where the browser context still exists — and passes them into the server path as OpenRTB user.ext.eids, which server-side adapters read and forward. This is the structural shift: identity resolution stays in the browser, and the auction stops caring where it runs. A bidder that accepts eids-carried identifiers loses far less by moving server-side than one that only recognizes its own cookie.

App traffic never had the problem. Cookie sync is a web mechanism; in-app and CTV requests carry device advertising IDs in the request itself. The surfaces where Prebid Server is mandatory are also the surfaces where it costs the least.

So the placement test is not “does server-side hurt identity” — it does — but “does this bidder’s demand depend on the ID that gets lost.” That question drives the hybrid rule below.

Bidder support: two catalogs, not one

Prebid.js and Prebid Server have separate adapter catalogs, separately maintained, and presence in one says nothing about the other. The Prebid Server overview counts 230+ server-side bid adapters; the Prebid.js bidder list is substantially longer, running to many hundreds of entries. Before planning any migration, check each bidder against the Prebid Server bidder list — a bidder without a server adapter stays client-side or leaves your stack.

Two s2sConfig behaviours matter here and are easy to miss:

  • Server-side aliases let the same bidder live on both sides split by format — the docs’ own example runs a bidder client-side for banner and server-side for video. The split can be per-format, not just per-bidder.
  • Multiple Prebid Server clusters are supported by passing an array of s2sConfig objects, with the constraint that the same bidder cannot appear in both.

Also verify the implementation flavour. Prebid Server exists as two codebases, Go and Java, and adapter and module availability differ between them. If a partner hosts your server side, their flavour determines which adapters you can enable — put it on the vendor checklist next to the per-bidder visibility question below.

How the timeouts nest when both are running

Timeout misconfiguration is the most common self-inflicted wound in hybrid setups. The semantics are precise, verified against the timeout feature docs and the s2sConfig reference. Four clocks, nesting strictly:

Clock Set where Rule
Failsafe timeout Page-level setTimeout outside Prebid.js Safety net that calls the ad server if all else fails; much larger than the auction timeout
Auction timeout bidderTimeout in Prebid.js Total time Prebid coordinates header bidding, client and server bids alike
Server auction window s2sConfig.timeout Sent to Prebid Server as tmax; docs advise 200–300 ms less than the Prebid.js timeout, i.e. roughly 50–75% of it
Bidder calls inside PBS Server config PBS shaves a safety buffer off tmax so it answers the page before the window closes

The defaults encode the nesting: unset, s2sConfig.timeout becomes 75% of bidderTimeout or 750 ms, whichever is smaller. The gap pays for the network round trip between page and server cluster — set them equal and server bids arrive after the client auction has closed, which reads in reports as the server “not bidding” when it is bidding into a void.

Three sharp edges the docs call out:

  • The client timeout is approximate. Prebid.js relies on JavaScript timers, and the setConfig reference warns that on a busy page a bid can slip in past the deadline; the auction closes immediately only once the overrun exceeds 200 ms.
  • The server is a single point of timeout. If Prebid Server times out, no server-side bidder submits a bid — all-or-nothing, where a slow client-side adapter costs you one bidder.
  • There is no server-side secondaryBidders. Client-side you can tell Prebid not to wait for named laggards; the docs note no server equivalent exists. Your lever there is the server’s timeout adjustment, which lowers the tmax passed to bidders.

One more budget thief: consent management, User ID modules, and real-time data modules all run before the auction and eat the same window. The docs recommend measuring the time between auction start and the first bid requests leaving; if pre-auction is a sizeable slice of the timeout, the fix is there, not in the timeout value.

Debugging: one side is inspectable, the other is not

Client-side Prebid is one of the most transparent systems in ad tech. Devtools shows every bidder call and response in the network tab. Append ?pbjs_debug=true to any page URL and Prebid.js surfaces its suppressed errors and verbose logging, per the troubleshooting guide; pbjs.getBidResponses() lists every bid, pbjs.getAllWinningBids() shows what won, and Professor Prebid — the project’s own Chrome extension — wraps it all in a UI. Anyone on your ad ops team can diagnose a misbehaving bidder on the live site with zero infrastructure.

Server-side, the browser shows one POST to /openrtb2/auction and one response. The per-bidder conversations — who was called, who timed out, what each bid — are visible only in the server’s logs, metrics, and analytics; the timeout docs’ phrasing is telling: diagnosing slow server-side bidders starts with “if you have server-side analytics available.” Host Prebid Server yourself and that observability is a system you must build and operate, exactly like the telemetry layer in our guide to setting up an RTB exchange — the auction is the easy part; knowing what it did is the work. If a vendor hosts it, your visibility is whatever their tooling exposes, which makes “show me per-bidder logs, timeout counts, and drop reasons for my traffic” one of the most discriminating questions to ask a prospective host.

The asymmetry has a second-order effect: client-side problems get found by anyone; server-side problems get found by whoever owns the dashboards. Moving demand server-side also moves the burden of proof.

Cost and operational ownership

Prebid.js is free the way a puppy is free. No servers, no hosting bill — the compute happens on your users’ devices — but you own a build pipeline, a module selection, a twice-yearly upgrade cadence, and a client-side release for every bidder change. The costs are engineering time and page performance, both real, neither itemized.

Prebid Server is a server, and the overview docs are blunt about what that means: it needs somewhere “scalable, distributed, and fast” to run, it needs a process for managing stored request configurations, the open-source community provides no configuration tools, and installing it “is not nearly as easy as Prebid.js.” Add Prebid Cache if you touch video or AMP. Then note the shape of the ongoing bill: every ad request becomes a fan-out of server-to-server calls you now execute — compute and egress scaling with your traffic and bidder count. The browser used to do that work for free.

The docs describe two acquisition paths: run it yourself, or use a hosted offering from a Prebid.org member. The build-vs-host evaluation in our white label ad exchange guide transfers almost verbatim — demand portability, data access, per-bidder transparency, exit terms — with one Prebid-specific addition: confirm the host’s flavour (Go or Java) covers your bidder list, and confirm log-level access to your own auctions rather than aggregate dashboards.

So which should you run?

Here is the advice the official docs won’t give. Match your situation to the first row that fits.

Run Prebid.js only when you are web-only, your bidder list fits the client-side envelope the docs describe (roughly 5–15 partners), your top earners are exchanges whose bids visibly depend on their own cookies, and you have no appetite for operating or contracting server infrastructure. This is the correct starting point for most publishers, and “we never needed Prebid Server” is a respectable end state for a mid-size portfolio. Do not adopt a server because a conference talk made you feel behind.

Add Prebid Server when any of these turns true: you monetize surfaces where it is mandatory (mobile app, AMP, CTV, audio, DOOH); you are refusing demand because the page cannot afford another client adapter; a Core Web Vitals or page-weight initiative is forcing JavaScript out of your pages; or you want to trial bidders without shipping client code per experiment. The trigger is a constraint you can name, not a milestone birthday for your wrapper.

Run Prebid Server only in two situations. First, app/CTV-first businesses — there was never a client-side option, and the identity penalty largely does not apply because device IDs travel in the request. Second, operations whose demand is genuinely not cookie-dependent: deal-based, contextual, or first-party-data selling where the third-party cookie was not carrying the value anyway. For open-web display monetized through cookie-synced exchange demand, pure server-side leaves match-rate money on the table daily; be able to say out loud why that trade is worth it before making it.

Run both when you are a web publisher at meaningful scale with a mixed bidder list — which is why most deployments converge here. The Prebid docs themselves point at this outcome, noting you can “use a combination of client and server processing to optimize your yield while still reducing latency.” The open question in a hybrid is never whether, but which demand goes where.

The hybrid topology real deployments converge on

The steady state: a short, defended client-side list, everything else through the server, and a written rule that decides placement so the list stops growing by default.

The rule: a bidder earns a client-side slot by paying for it — with bid rate and CPM that demonstrably depend on being client-side and that survive the page cost it imposes. Everything else runs server-side.

Applied per category:

Demand Side Why
Top exchange bidders whose CPMs depend on their own cookie Client The identity you’d lose is the value; they pay the page rent
Bidders reachable through User ID module eids Server The ID travels in user.ext.eids either way; nothing is lost
Long-tail and low-share bidders Server Incremental revenue doesn’t cover a client slot’s page cost
New bidder trials Server Add and remove demand without shipping page code
Video demand Server (or alias-split) Heavy responses; Prebid Server caches VAST server-side
Deal/PMP, contextual, first-party-data demand Server The bid doesn’t depend on the cookie that gets lost
Mobile app, AMP, CTV, audio Server No client-side option exists

Two mechanisms keep the split cheap. Server-side aliases handle bidders that deserve different placement per format — the docs’ banner-client, video-server pattern. And because server-side bidders cost the page nothing, the burden of proof flips: the client list needs periodic re-justification, the server list doesn’t. Re-run the placement test quarterly and demote any client-side bidder that stops covering rent.

One governance note: a hybrid puts your monetization logic — floors, routing, bidder placement — in two places with two release processes. Decide early where the single source of truth sits, and make both sides read from it.

There is a revenue consequence of the split that gets lost among the latency arguments: enrichment can only happen server-side. Resolving geography from the connection, attaching the identity a buyer will recognise, applying anything learned from previous auctions — none of it is available to a script running in a browser, and none of it should be, since it would mean shipping that logic to every page and trusting whatever came back. Move the auction server-side and every request can be enriched before any buyer sees it, consistently, with no additional page weight. Since a request a buyer can locate and recognise is worth more than the identical one they cannot, that is a revenue argument wearing an architecture argument’s clothes.

How do you migrate demand server-side without losing revenue?

Slowly, one bidder at a time, with a baseline you trust — because the classic regression, the match-rate drop, is guaranteed by mechanism, and the only question is whether you can see it, size it, and mitigate it per bidder.

Before moving anything, capture at least two representative weeks of per-bidder baseline: bid rate, win rate, average CPM, timeout rate, and — the one that decides arguments later — revenue per thousand requests per bidder. Capture page metrics too (JS weight, ad latency): the payoff you’re buying is on that side of the scale. If you cannot measure per-bidder revenue per thousand requests, fix that first; without it, the post-move debate over whether a bidder “got worse” is unwinnable in either direction.

Move first, in order: bidders that frequently time out client-side (already losing auctions; the server fan-out can only help); long-tail bidders whose slot cost exceeds their contribution; video demand, which benefits from server-side VAST caching; and every new bidder trial from now on. Move last, or never: your top cookie-dependent earners — only after eids-carried identity has proven out on lesser bidders, and only with an A/B to quantify the hit.

Expect these regressions, in this order:

  1. Bid rate on moved bidders drops — the sync table is cold. The first auctions after a user arrives carry no synced IDs (the docs advise delaying syncs past the first auction, so the first pageview is structurally unmatched); coopSync left on and a sane userSyncLimit fill the table across pageviews. Judge match-dependent metrics after days, not hours.
  2. CPMs sag even where bid rate holds — partially matched users draw lower bids. Mitigation is the User ID module feeding user.ext.eids, and, bluntly, accepting that some cookie-era value does not survive the trip for some bidders. That is information, not failure: it tells you which bidders belong client-side.
  3. Server bids vanish entirely — almost always the timeout nesting: s2sConfig.timeout too close to bidderTimeout, so server responses arrive into a closed auction. Check the nesting before blaming demand.

Then compare honestly. Split traffic if you can; otherwise compare against baseline, per bidder, on revenue per thousand requests rather than raw CPM — volume and mix shift during a migration and averages lie. Sum both sides of the ledger: moved-bidder revenue delta against page-performance gains and the freed client slots. A bidder that lost a little but returned a slot to a bidder that gained more is a win that per-bidder metrics alone report as a loss.

Give each moved bidder a decision window of a few weeks, then decide: keep server-side, revert, or alias-split by format. “We’ll revisit it someday” is how migrations strand revenue.

Key takeaways

Point Detail
One change drives everything Who calls demand: the user’s browser, or your server
Client-side sells identity Bidders read their own cookies; best match rates, best CPMs on cookie demand
Server-side sells scale More bidders, near-zero page cost — paid for in sync-chain match-rate loss
Some surfaces aren’t a choice App, AMP, CTV, audio are Prebid Server territory, full stop
Timeouts must nest s2sConfig.timeout 200–300 ms under the Prebid.js timeout, or server bids arrive into a closed auction
Transparency is asymmetric Client-side debugs in devtools; server-side truth lives in logs someone must own
The hybrid rule A bidder earns a client slot by paying page rent with identity-dependent revenue; everyone else goes server-side
Migrate one bidder at a time Baseline revenue per 1k requests per bidder first; expect the match-rate dip; decide per bidder within weeks

Running both sides on Floxis

The reason we can make recommendations the docs won’t is that we operate both halves of this article as products, and the split above is the one our deployments live on.

Floxis Prebid.js infrastructure is the client side run as something you own: publisher-owned Prebid.js adapter infrastructure that works across wrappers without tying you to any single wrapper or vendor. The wrapper runs the client-side auction; your adapter routes requests; pricing, routing, and governance execute in server-side logic behind it — without adding client-side execution or browser latency. That gets you the levers this article kept pointing at: consistent pricing and floor control across demand, traffic shaping and routing on rules you set, supply path control, request volume management, and controlled experimentation that keeps production traffic out of harm’s way.

Floxis Prebid Server infrastructure is the server side run the same way: server-side auction infrastructure built for high-volume production, with centralized pricing, routing, and governance, operational tooling for monitoring and control, and support for exactly the incremental rollout and validation path the migration section describes. It integrates with your existing demand partners and ad server, and it is operated as infrastructure, not a managed black box — which, after the debugging section, you can read as a position on whose logs these are.

If you are working out where the client/server line belongs in your stack — or whether the answer is one side, the other, or the hybrid — request a technical walkthrough and we will map it against what you already run.

Sources worth bookmarking