We've sat in enough architecture reviews to know how this conversation usually starts: someone pulls up a benchmark showing gRPC beating REST by some multiple, and the room treats that number as the decision. It isn't, not on our platform. An internal microservice pushing 10,000 requests a second across a polyglot team is solving a different problem than a Next.js frontend calling a public API that's supposed to be cached at our global network, and conflating the two is where these debates go wrong before they even start.
The request path decides first. Browser support and CDN caching constrain which protocol is viable long before serverless execution cost or a throughput benchmark gets consulted. Vercel's public API is a REST API, served over HTTP/1 and HTTP/2 with SSL, and gRPC has never been one of our inbound protocols. That's not an oversight. It's downstream of what the platform is built to do: cache responses at the edge, terminate in a browser, and treat GET and HEAD as safe operations that a CDN can store. gRPC breaks those assumptions by design, which is the real reason this decision starts with the request path and only gets to operational cost after.
Copy link to headingKey takeaways
REST is the default whenever a request touches a browser, routes through Vercel's inbound Function path, or is meant to use CDN caching, because caching depends on
GETandHEADresponses, and browsers can't speak raw gRPC without a proxy in front.gRPC's performance edge is real but conditional. It narrows, and sometimes reverses, across payload sizes, and it runs into streaming and load-balancing constraints that benchmark charts don't show.
gRPC's proven home is internal service-to-service traffic, the same boundary that CNCF guidance and our own platform architecture converge on independently.
Running gRPC in production costs more than the protocol itself. L7-aware load balancing and stream debugging are the bill that comes due after adoption, not before.
Secure Compute is how we bridge the two worlds on Vercel. REST stays at the edge, and gRPC or any private protocol runs behind a private VPC connection.
Copy link to headingWhat is REST?
REST is an architectural style for HTTP APIs, not a specific technology. It means resources addressable by URL, verbs GET, POST, PUT, DELETE) that map to intent, and payloads that travel as JSON. That's the whole contract, and it's shallow on purpose.
The shallowness is what makes it work everywhere. Every HTTP cache, every browser, every proxy, and every piece of debugging tooling built in the last twenty-five years already speaks this language. In our experience, teams underestimate how much of REST's value comes from the ecosystem behind HTTP, not anything intrinsic to the REST style itself. GET and HEAD are safe and idempotent by spec, which is the property a CDN needs to treat a response as cacheable and a browser needs to prefetch or retry it without side effects.
Vercel's own public API is a REST API for exactly this reason. It's exposed over HTTP/1 and HTTP/2 with SSL, and any client that can make an HTTPS request can call it without a generated stub or a proxy standing in front.
Copy link to headingWhat is gRPC?
gRPC is a contract-first RPC framework built on HTTP/2. You define services and messages in a .proto file, generate client and server stubs in whatever languages your services are written in, and the wire format is Protocol Buffers, a binary encoding rather than text.
The tradeoff is the mirror image of REST's. Where REST gives you a shallow, universally supported contract, gRPC gives you a strict, statically typed one. We've watched teams choose gRPC specifically because a schema change breaks the build instead of failing silently three services downstream in production, and that's a real advantage in a system where a dozen teams own a dozen different pieces of the same call graph. HTTP/2 is a hard requirement, not an option, because gRPC depends on multiplexed streams over a single connection to support its four call shapes, including unary, server streaming, client streaming, and bidirectional streaming.
None of that is free. The HTTP/2 requirement locks out anything that speaks HTTP/1.1 only, and Protocol Buffers means every consumer needs the generated stub, not a browser and a URL.
Copy link to headingHow REST and gRPC differ at the mechanism level
Most engineers we talk to want to frame this as a performance question. It isn't, not at the root. REST is built for HTTP infrastructure: caches, proxies, browsers, and every debugging tool that speaks plain HTTP. gRPC is built for controlled internal networks where schema contracts and streaming semantics matter more than ecosystem compatibility. Everything downstream, caching, browser support, and load balancing, falls out of that one structural difference.
Copy link to headingPerformance benchmarks depend on test conditions
We don't put much weight on a benchmark until we know what it excludes. Benchmark results in this space contradict each other, which makes the test conditions the actual finding. In one methodology, gRPC's performance advantage decreases for larger messages compared to REST and SOAP. In another, gRPC performs better than REST, the larger the transmitted data gets. Both used defensible methodologies. The results are sensitive to test conditions, which should tell you something about how much to trust any single number without reading the setup behind it.
The benchmark genre carries its own caveat. Tests that isolate serialization, use flat payloads, or run everything on localhost tend to overstate protocol effects, because they strip latency and connection behavior out of the request path entirely. Streaming is supposed to be gRPC's signature strength, and it still carries a real constraint: streams cannot be load-balanced once they've started, and they're genuinely hard to debug after a failure.
When the request path touches our global network, these platform constraints settle the question before a throughput comparison gets to.
Copy link to headingWhy REST caches at the edge and gRPC doesn't
We operate the cache, so this asymmetry isn't theoretical for us. Our CDN caches a Function response when the request is GET or HEAD, and the body is under 10 MB. The response also has to carry Cache-Control with s-maxage or stale-while-revalidate, with no set-cookie or no-store (CDN cache docs). Those rules trace back to RFC 9110, where GET and HEAD are safe and idempotent, and the method plus URL forms a stable cache key. stale-while-revalidate is the core mechanism we use to cut both latency and serverless execution count at once: serve the cached response instantly, regenerate in the background, and the visitor never waits on the regeneration.
gRPC fails those conditions by design, not by oversight. All gRPC RPCs use HTTP POST by default, which is neither safe nor idempotent, and standard gRPC implementations attach no Cache-Control headers at all. Streaming responses compound the problem: an open-ended HTTP/2 stream has no discrete boundary for a cache to store. This isn't a gap anyone's rushing to close, either. It's been tracked in grpc/grpc issue #7945 since 2016 and is still labeled disposition/stale. The one workaround, a custom interceptor marking a method "safe" at the transport layer, sits outside the standard pattern, which tells you how rarely anyone actually needs it.
The classic mistake we see is treating gRPC as a drop-in performance upgrade for a public route, without checking whether the route was ever meant to be cached. On Vercel, gRPC traffic proxied through the platform never meets cacheable response criteria, so every single request hits the origin function and pays full serverless execution cost plus origin latency. ISR gives you request collapsing, durable storage, and roughly 300ms global purges as documented, standard behavior. None of that is available to a protocol that can't produce a cacheable response in the first place.
We made the identical call in our own observability stack, for what it's worth. Trace Drains use OTLP/HTTP exclusively. OTLP/gRPC on port 4317 sits outside that support. We still send binary protobuf over OTLP/HTTP, because the encoding is efficient for high-volume trace data, and HTTP keeps the transport aligned with the rest of the platform. Efficient encoding and cacheable transport aren't in tension. They're two different layers of the same decision.
Copy link to headingWhy gRPC needs a workaround to reach a browser or a Function
Browsers block raw gRPC at the API level. It can't be implemented with browser APIs, because browsers don't expose enough control over HTTP/2 to do what gRPC needs. gRPC-Web exists as the bridge, and it's worth being precise about what kind of bridge it is. It requires a mandatory proxy, Envoy by default, and it drops client-side and bidirectional streaming because the browser constraint doesn't go away, even though you added a proxy.
The mistake we've watched teams make here is assuming gRPC-Web is gRPC with a browser wrapper. It isn't. You've added an operational hop (the proxy), lost half of gRPC's streaming model, and you still don't have a browser that speaks gRPC natively. If the calculus was "gRPC's contract discipline is worth it," that calculus has to be redone once the proxy and the lost streaming modes are actually on the ledger.
The serverless boundary is documented rather than assumed. Vercel documents how Vercel Functions handle inbound requests, and outbound gRPC client calls from a Function sit outside that documented boundary entirely. Our supported inbound protocols are HTTPS, HTTP/1.1, HTTP/2, and response streaming, and gRPC is absent from that list. We're not an outlier here. AWS Lambda can't be a gRPC target in ALB target groups, and Cloudflare imposes a 30-second proxy write timeout and a 120-second proxy read timeout, with a 900-second idle timeout, and the read timeout is only configurable on Enterprise. Long-lived gRPC streams and edge proxies are in tension across every platform we know of, ours included, because the same properties that make an edge proxy fast and cacheable are the properties a long-lived stream can't tolerate.
Connect RPC is the most practical mitigation we've seen for this. It supports HTTP/1.1, HTTP/2, and HTTP/3, works in browsers without a proxy, stays backward-compatible with gRPC, and has joined the CNCF. It still can't remove the browser's bidirectional streaming constraint, because that constraint lives in the browser, not in the proxy layer. For anything deployed to Vercel where a browser or a Function is the client, REST, with SSE for server push, is the path that works without a bridge.
Copy link to headingWhy gRPC's load balancing model turns a protocol win into an operations problem
L4 load balancers operate at the connection level. gRPC multiplexes every call over one TCP connection, so once a client connects, all of its gRPC calls route to that single endpoint for the life of the connection. Correct gRPC load balancing needs L7 routing that opens an HTTP/2 connection to each destination and balances individual requests across them, which is a different piece of infrastructure than most teams already have running.
The anti-pattern here is assuming gRPC is "just HTTP/2" and that your existing L4 setup will handle it the way it handles everything else. It won't, because HTTP/2's connection reuse, the exact feature that makes it efficient, is the same feature that breaks connection-level load balancing. The cost of that assumption shows up after rollout, not during it. Square's migration needed custom load balancing and name resolvers for grpc-go and grpc-java, and after partially building the required pieces, the migration was put on ice. Dropbox built Robinhood, a custom PID-controller load balancer, because standard balancers kept pushing traffic toward already-degraded nodes in a feedback loop nobody designed for.
Streams add their own limit on top of this: they can't be load-balanced once they've started, they're genuinely hard to debug after a failure, and that combination reduces scalability in ways that don't show up until you're already past the point of easy fixes. None of this means gRPC is broken. It means gRPC's protocol wins and gRPC's operational cost are two separate line items, and adoption should wait until the infrastructure to run it correctly is actually in place, not after.
Copy link to headingWhen to use REST
If the request originates in a browser, or if it's meant to be cached anywhere along the path to one, REST is the default, and we don't treat that as a close call. Caching, browser support, and standard tooling all work without a bridge, a proxy, or a custom interceptor. That's the entire value proposition: nothing extra has to be built for the common case to work.
The cost is real and worth noting. REST's contract is loose enough that a client and server can drift out of sync silently, where gRPC would catch the same drift at compile time. You give up native bidirectional streaming, and you're relying on convention, OpenAPI specs, or manual review to keep a large team's endpoints consistent, rather than a generated stub enforcing it for you. For anything public-facing, browser-reachable, or CDN-cached, that's a trade most teams should take without much agonizing. For an internal call graph, a dozen teams are actively co-developing, it's a trade worth questioning.
Copy link to headingWhen to use gRPC
The one place we don't second-guess gRPC is internal, high-frequency, polyglot service-to-service traffic where you control both ends of the call. That's where gRPC earns its overhead, and a natural split puts REST at the edge and gRPC inside the service graph behind it. The production record backs that split rather than theorizing it. A large internal service platform can cut client creation time from weeks to minutes, with time to market reduced by orders of magnitude. Polyglot support and contract-driven development make gRPC fit for multi-language service graphs, latency and throughput gains over REST show up in Uber’s gRPC-based push platform migration, and long-lived server push fits patterns where a client keeps an open gRPC connection while the server pushes updates continuously.
Every one of those success stories shares the same precondition: a controlled network and co-developed services, where the .proto contract matters more than ecosystem compatibility. That precondition is the actual gate, not the protocol. Genuine streaming needs, L7 load-balancing infrastructure, and a service mesh already in place define the same boundary from the infrastructure side. If you don't have those yet, you don't have a gRPC-ready system yet either, no matter how good the benchmark numbers look.
Copy link to headingBuilding with REST and gRPC on Vercel
This is the boundary we point teams to when they ask how to get both halves working without forcing one protocol to do a job it wasn't built for. Our platform is built for the edge half of this pattern, and we built Secure Compute so the internal half stays reachable without punching a hole in either boundary. Secure Compute creates private VPC connections between Vercel Functions and backend infrastructure, including Kubernetes clusters, internal APIs, and databases, with dedicated static IPs so backend access controls admit only your specific Vercel infrastructure. It's available on Enterprise plans, which is the cost of admission for this pattern. The BFF layer pattern puts the BFF layer in Next.js, where Route Handlers aggregate and Server Components fetch upstream, and Routing Middleware handles auth in front of both.
For simpler cases, Vercel Services routes internal calls between a frontend and a Python or Go backend so traffic never leaves our network at all. Underneath all of it, Fluid compute, which powers more than 45 billion weekly requests, uses persistent TCP tunnels for bidirectional streaming of invocation data internally. Our own architecture runs on connection semantics that look a lot like gRPC's, even though the inbound surface is HTTP. That's not a coincidence. The "edge REST, internal gRPC" split is the architecture CNCF guidance and our own platform design arrived at independently, which is a stronger signal than either one alone.
Copy link to headingMatch the protocol to the request path
If you look across everything above, what's actually going on isn't a protocol comparison at all. It's a question of where the edge boundary sits in your system. Once you've placed that boundary correctly, the protocol isn't really a choice anymore. It's the name for what your layer already requires: REST at the edge because that's what caches, browsers, and CDNs speak, and gRPC behind it because that's what a controlled, co-developed service graph actually needs.
gRPC readiness is operational, not aspirational. Put L7 load balancing behind a service mesh or a proxy like Envoy or Linkerd, then assign real ownership for .proto schema contracts across every team that touches the graph. Every production example in this piece shares the same sequence, and it's the wrong one: adoption arrived before the load-balancing and debugging infrastructure did. Do it in the other order.
Behind Secure Compute, your private backends get to choose their own protocol. At the edge, the protocol support and caching rules already document the path that works.
Copy link to headingFAQ
Copy link to headingCan I use gRPC outbound from a Vercel Function as a client?
Vercel documents how Vercel Functions handle incoming requests. Outbound gRPC client calls from Functions sit outside that documented boundary. Connect RPC is the more documented path for calling gRPC-compatible backends from a Vercel Function.
Copy link to headingDoes Vercel support WebSockets if gRPC is not supported?
Yes. WebSocket support entered Public Beta on June 22, 2026, and requires Fluid compute, which has been the default for projects created after April 23, 2025.
Copy link to headingDoes REST get faster on HTTP/3?
Potentially, when the HTTP infrastructure in front of an API supports HTTP/3. REST's application contract stays the same when the server, CDN, and browser negotiate a newer HTTP transport. gRPC's documented baseline stays HTTP/2 regardless.