Frontend Architecture

[Frontend Architecture] BFF Design

[Frontend Architecture] BFF Design

About this article

As the sixth installment of the “Frontend Architecture” category in the series “Architecture Crash Course for the Generative-AI Era,” this article explains BFF.

A design pattern proposed by SoundCloud in 2011 as the answer to the problem of “no one is happy with a shared API.” This article covers the BFF background, typical responsibilities, implementation patterns (Next.js API Routes/tRPC/Hono), BFF vs GraphQL, antipatterns, and AI-era type sharing - presenting the operational iron rule of “keep it thin, introduce it only when needed.”

Before you read this

This article is mostly about how the browser side — the screen — works. If IT vocabulary is unfamiliar, reading the primers "How a Web Service Works" and "Programs and APIs" first makes it far easier to follow. You can also look anything up in the glossary as you read.

What is BFF in the first place

BFF Implementation Pattern Selection

BFF is, roughly speaking, “an intermediate layer that provides the optimal API per client.”

Imagine a hotel concierge. For business guests, they arrange meeting rooms and taxis; for tourists, maps and restaurant reservations — delivering only the info each guest needs in the optimal form. Handing everyone the same thick guidebook (a general-purpose API) leaves most pages unused. BFF plays this concierge role, shaping and returning only the data needed for each client — Web, mobile, TV, etc.

Why BFF is needed

Without BFF, one general-purpose API would serve all clients (Web, mobile, TV), but each client has completely different screen sizes, bandwidth, and data requirements. The result: returning the same response to everyone creates an API that’s optimal for no one. The frontend discards unneeded data and supplements missing data with extra requests — inefficiency compounds, degrading both performance and security.

It used to be common to share “one general-purpose API” across all clients, but Web/mobile/TV each have completely different screen sizes, required data, and auth requirements, leading to the problem of “no one is happy with a shared API.” BFF solves this through “an intermediate layer that provides the optimal API per client.”

The BFF philosophy is to separate responsibilities per client.

BFF came out of exactly that situation.

The BFF pattern was proposed around 2015 by SoundCloud engineers, then spread through adoption in mobile apps at Netflix, Spotify, Twitter, and others. The background is that “as a result of microservices proliferation,” frontends came to need to call dozens of different APIs.

Old: one general-purpose API shared by all clients
[Web]   ──┐
[Mobile]─→ [single API] ──→ [Backend]
[TV]    ──┘

The problems with this structure are three: “Web changes affect Mobile,” “each client fetches fields it doesn’t use,” and “auth requirements per client get mixed in.” Web and mobile also have different bandwidth constraints, so a 1-API share was recognized as unrealistic - and BFF was born.

Typical BFF responsibilities

A Shared API vs a BFF (a per-client API) Like a hotel concierge: shape the information for each client before handing it over Shared API (traditional) Web Mobile TV One API Everything in it Backend Problems A web change affects mobile Returns large amounts of unused data Authentication requirements get mixed An API that is optimal for nobody BFF (a per-client API) Web Mobile TV Web BFF App BFF TV BFF Backend services Benefits Returns only the data each client needs A change does not ripple to other clients API aggregation (parallel calls → one response) Auth and sessions managed per client Keep the BFF thin. Business logic stays in the backend Typical: Next.js API Routes / Server Actions as the BFF layer

The BFF ideal is “a thin server doing front-end’s grunt work.” Business logic stays in the backend services, and BFF dedicates itself to display, formatting, and proxying - that’s the healthy design.

ResponsibilityContent
API aggregationGather from multiple microservices into one screen’s worth
Data shapingConvert to a screen-friendly format, drop unneeded fields
Auth/sessionToken management, cookie-ization, hiding secret keys
CacheHTTP cache for API responses, edge cache
Rate limitDDoS countermeasures, API abuse prevention

In particular, API aggregation matters - when displaying one screen requires separately calling “user info, order history, recommendations, ads,” BFF can “parallel call” these behind the scenes and bundle them into one response. The browser doesn’t have to make 5 requests, and felt speed improves dramatically.

A typical composition looks like this.

The most common composition uses a full-stack FW like Next.js as the BFF layer. By placing it in the same repo as the frontend, “the same team handles UI development and API shaping.”

[Browser]
   ↓ (same-origin communication, Cookie)
[Next.js API Routes / Server Actions]  ← BFF layer
   ↓ (mTLS / internal network)
[Microservices / external APIs]

The BFF owner is the frontend team, and placing it in the same repo as the frontend is the rule. Since BFF changes alongside screen changes, separate repos balloon the change cost. Living together means “change a screen and PR the BFF in the same flow.”

The effects show up in three places.

Inserting a BFF gives three concrete effects, large in scope. These can’t be obtained by simply hitting APIs directly.

1. Security.

Hitting external APIs directly from the browser exposes API keys and auth tokens browser-side. With BFF in the middle, tokens stay only on the server side, and only an httpOnly Cookie is handed to the browser. The risk of tokens being stolen via XSS (Cross-Site Scripting, a script injection attack) is fundamentally eliminated.

2. Performance.

You can “parallelize the calls to multiple microservices on the BFF” and bundle them into a single response. Browser-to-BFF is fast (same-origin), BFF-to-backend is fast (internal network) - “double optimization” applies.

3. Frontend development speed.

Because the frontend team can “decide the API shape themselves,” they can cleanly format only the data needed for the screen. The back-and-forth of asking the backend team to “add this field” disappears.

Implementation patterns — framework-embedded is the mainstream

BFF implementation methods are varied. Framework-built-in is easy; type-safe options like tRPC are popular as the edgy choice.

PatternNotes
Next.js API Routes / Server ActionsMost prevalent in the React family
Nuxt Server RoutesEquivalent option in the Vue family
Remix Loader/ActionWeb-standards-focused design philosophy
tRPCGodlike dev experience via TypeScript type sharing
Hono + Cloudflare WorkersEdge BFF, low latency
Dedicated Express/FastifyLightweight, repo-separate from FE

With the arrival of Next.js Server Components and Server Actions, the need to “build a separate BFF” has diminished, and BFF becoming a built-in framework concern is becoming the mainstream. For simple use cases, Next.js alone is sufficient.

tRPC is the type-safe end of that range.

tRPC is a mechanism for fully sharing TypeScript types between server and client - rapidly spreading in recent years. Without schema definitions like REST or GraphQL, you can write APIs as if calling functions.

// server side
const appRouter = router({
  user: {
    byId: procedure.input(z.string()).query(({ input }) => ...)
  }
})

// client side - server's types apply automatically
const user = await trpc.user.byId.query("u_1")
// user's type is auto-inferred, input validated by Zod

Benefits: No schema files, editor completion via types, runtime validation (integration with Zod (a TypeScript-only schema definition library)). On TypeScript full-stack projects (FE/BE coexisting in one project), it shows overwhelming dev efficiency.

Next.js + tRPC + Zod is the strongest modern type-safe full-stack composition.

BFF and GraphQL are often compared, and they solve overlapping problems.

GraphQL is a technology solving similar problems to BFF, and the choice between them is often confusing. The two are “not exclusive” - they can be combined - but their strengths differ, so use them for the right scenarios.

ViewpointBFFGraphQL
ImplementationEndpoints per screenSingle endpoint
Learning costLightHeavy (design/operation hard)
FlexibilityServer-side fixed shapingClient queries freely
CacheEasy via HTTP standardsRequires separate design

“When BFF fits”: few clients (Web/mobile), screen design is stable. “When GraphQL fits”: diverse clients (mobile/TV/external partners) want to query freely.

For single-client-centric use, BFF; for diverse clients querying freely, GraphQL is the rule.

The standard approach to session management

For auth when using BFF, the standard is “only Cookie to the browser, real tokens managed behind the BFF.” This composition maximizes both XSS resistance and token management.

1. Browser → IdP (Identity Provider, the auth foundation, e.g., Auth0) gets ID token
2. ID token sent to BFF
3. BFF verifies, issues httpOnly Session Cookie
4. Subsequent API calls are Cookie-based (browser side never touches JWT)
5. BFF manages Access Token / Refresh Token behind the scenes

The decisive benefit of this composition is never exposing JWTs to the browser. The chance of Session Cookie being stolen via XSS is eliminated by httpOnly, and even if stolen, the server can revoke immediately. Compared to designs that put JWTs in the browser, security reaches one rank higher.

BFF + httpOnly Cookie is the safest composition for modern Web auth.

How to choose — a phased decision table

The most important judgment for BFF is whether to introduce it, and it auto-derives from scale and architecture. Here’s the practical phased table.

PhaseScale/architectureBFF treatmentImplementation method
1. Personal/MVP~1,000 MAUUnneeded (direct API calls)fetch + useState
2. Early startup~10,000 MAU, single backendThin BFF (FW built-in)Next.js API Routes / Server Actions
3. Mid-size SaaS10,000-100,000 MAU, multiple microservicesAggregation BFFtRPC + Next.js
4. Large100,000+ MAU, multi-teamDedicated BFF layer (per screen)Dedicated Express/Fastify + gRPC
5. Multi-clientWeb + Mobile + TVPer-client BFFEach client dedicated
6. Edge-distribution priorityGlobal, low-latency-focusedEdge BFFHono + Cloudflare Workers

“The substantive lower bound for adopting BFF is when microservices + aggregation across 3+ services is required.” Below that, the thin layer of Next.js API Routes / Server Actions is enough; spinning up a dedicated BFF server is over-design.

BFF goes in when needed. Adopting “for the future” backfires on operational costs.

Three scenarios

If you are building solo or at a startup

At this size a BFF is simply unnecessary. Calling the API directly from the frontend, or using Next.js Server Actions as a thin layer, covers it entirely. Standing up a dedicated BFF server “for the future” tends to add operational cost and nothing else.

Personal / Startup: Ship in One Month Is Correcten.senkohome.com/arch-intro-case-startup/

If you are a small or mid-size SaaS

Once several backend services have appeared, aggregating them behind a unified BFF built on tRPC and Next.js, with types shared across the boundary, is a good move. Types line up automatically between frontend and backend, so bugs caused by API-specification drift are caught at build time — that is the large benefit here.

Small-Mid SaaS - Lean on Managed and Run with Few Peopleen.senkohome.com/arch-intro-case-saas/

If you are a large enterprise

Only when you have multiple clients — web, mobile, TV — and multiple teams does a dedicated BFF layer per client become justified. Give each team ownership of its own BFF, and keep it loosely coupled to the backend microservices through an API contract (OpenAPI or a GraphQL schema).

Large-Enterprise Core: Design That Holds Up for Yearsen.senkohome.com/arch-intro-case-enterprise/

AI decision axes — Sharing types maximises AI generation accuracy

The tRPC and Zod combination

tRPC automatically shares types between frontend and backend, and when validation is defined with Zod schemas, API input/output types automatically propagate to the frontend. For AI, “the list of available APIs and each parameter’s types” are explicit in the codebase, so API-call generation accuracy rises dramatically.

With hand-written REST APIs + dual-maintained type definitions, when frontend types and server implementation diverge, AI can’t judge which to trust and risks generating inaccurate code.

Cases where Server Actions remove the need for a BFF layer

With Server Actions from Next.js 14 onwards, form submission and data mutation can be called directly as server-side functions. There is no need to stand up a thin BFF layer separately — the server-side processing closes inside the frontend file. For AI this is easier too: frontend and server processing sit in the same file, so the context is straightforward to grasp.

Pitfalls and forbidden moves

Here are the six most dangerous entrances to the bloat that turns a BFF into the main system.

Forbidden moveWhy it is bad → what to do instead
Writing business logic in the BFFwithin a year it duplicates the core service → leave discount calculation and stock allocation in the backend
Letting the backend team own the BFFevery screen change needs cross-team coordination → give ownership to the frontend team
Splitting the BFF and the frontend into separate repositoriesa screen change then needs two pull requests → keep them in the same repository
Reimplementing authentication in the BFF, one per BFFauthentication bugs appear in every one of them → consolidate on a single authentication platform
Leaving the BFF timeouts unseta backend failure takes the BFF down with it → set them on every outbound call
Adopting a BFF early because “a BFF is the modern way, even at small scale”it is over-design that adds only operational load → do not introduce one at a size where Server Actions are enough

Author’s note — “the day the BFF became the main system”

There’s a story told at study groups about a site that piled discount calculations and inventory reservations into Next.js API Routes “because the backend team was slow to respond” - resulting in a reverse structure a year later: the BFF effectively became the main system, and the real Backend became a CRUD storage. Fixing required changing both, and they exhausted themselves on dual maintenance.

The scene of “let’s just write it quickly in Next.js” producing a horrifying reversal six months later is witnessed at many sites. The scary part of this accident: at the individual PR level, it was just “writing a little”. A one-line discount calculation became inventory reservation in a month, point calculation in six months, and “most of the core logic” in a year - the typical pattern.

BFF “looks harmless at the entrance, but always bloats if left alone.” The lesson “don’t put a vault in the entrance lobby” was born from accumulating these accidents.

The iron rule for BFF is keep it thin. Pound on the early signs of business logic creeping in.

What to decide - what is your project’s answer?

For each of the following, try to articulate your project’s answer in 1-2 sentences. Starting work with these vague always invites later questions like “why did we decide this again?”

  • Whether to set up a BFF (judge from scale and architecture)
  • Implementation tech (Next.js / tRPC / Hono / dedicated server)
  • Repo composition (coexist with FE / separate repo)
  • How to handle auth (Cookie / Bearer / Session)
  • Cache strategy (in BFF / CDN / which has priority)
  • Logging and monitoring (correlation IDs, OpenTelemetry)

Summary

This article covered BFF, including responsibilities, implementation patterns, differences with GraphQL, antipatterns, and type sharing.

Keep BFF thin, judge necessity from scale, and maximize AI productivity through type sharing + same repo. That is the practical answer for BFF design in 2026.

Next time we’ll cover authentication and authorization (frontend side) (Cookie/JWT storage, XSS/CSRF countermeasures).

Back to series TOC -> ‘Architecture Crash Course for the Generative-AI Era’: How to Read This Book

I hope you’ll read the next article as well.

📚 Series: Architecture Crash Course for the Generative-AI Era (42/95)