Frontend Architecture

[Frontend Architecture] BFF Design - Keeping Backend For Frontend Thin

[Frontend Architecture] BFF Design - Keeping Backend For Frontend Thin

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 (Backend For Frontend).

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.”

“Shared API” makes nobody happy

flowchart LR
    WEB[Web<br/>large screen, SEO-needed] --> BFF_W[Web BFF<br/>OG image/SEO/full info]
    MOBILE[Mobile<br/>narrow bandwidth] --> BFF_M[Mobile BFF<br/>lightweight, minimal]
    TV[TV App<br/>distinct UI] --> BFF_T[TV BFF<br/>large image/vertical list]
    BFF_W --> MS{Microservices}
    BFF_M --> MS
    BFF_T --> MS
    MS --> SVC1[Auth]
    MS --> SVC2[Catalog]
    MS --> SVC3[Cart]
    MS --> SVC4[User]
    classDef client fill:#fef3c7,stroke:#d97706;
    classDef bff fill:#dbeafe,stroke:#2563eb,stroke-width:2px;
    classDef ms fill:#fae8ff,stroke:#a21caf;
    class WEB,MOBILE,TV client;
    class BFF_W,BFF_M,BFF_T bff;
    class MS,SVC1,SVC2,SVC3,SVC4 ms;

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.

How BFF was born

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

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.

Typical BFF composition

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.”

Why BFF is effective

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

1. Improved 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. Improved 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 dev efficiency

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.

BFF implementation patterns

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.

Type-safe BFF with tRPC

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 vs GraphQL

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.

Session-management standard

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.

BFF antipatterns

BFF is convenient, but if you don’t keep it appropriately thin, it bloats rapidly into new debt. Without grasping these typical failure patterns, the BFF itself becomes another monolith.

  • Bloating: business logic flows into the BFF and duplicates with core services. Fixing requires changing both
  • Siloization: each BFF starts its own user management or cache, and consistency is lost
  • Cache placement problem: confusion about where (in BFF / in backend / on CDN) and how long to cache
  • Auth duplication: each BFF independently implements auth, and auth bugs sprout everywhere

BFF is a thin layer for the frontend. Bringing in business logic duplicates with core services and breaks down.

”The day BFF became the main system” (industry stories)

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.

Phased BFF adoption decision matrix

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.

BFF design’s pitfalls and forbidden moves

Here are the typical accidents in BFF operation. All of them are entry points to BFF becoming the main system.

Forbidden moveWhy it’s bad
Write business logic in BFFDuplicates with core service in a year. Don’t put a vault in the entrance lobby
BFF owned by the backend teamEvery screen change requires cross-team coordination. Frontend team ownership is the rule
Separate repos for BFF and FEScreen change requires 2 PRs, accidents at sync timing
No cache strategy in BFFOrigin load concentrates, BFF becomes the bottleneck
Re-implementing auth in BFF (each BFF independently)Auth bugs sprout everywhere. Lean on unified auth
Connect BFF and FE with REST + hand-typed typesTypes are split, dual-managed type definitions. Fully share via tRPC
No timeout in BFFBackend failures topple the BFF, avalanche cascade
Update without optimistic locking in BFFConcurrent updates cause lost updates, data corruption
Letting non-frontend clients hit the BFF tooThe point of BFF disappears. Maintain use-case specialization
Unify multiple microfrontends on one BFFTeam independence is lost. One BFF per frontend

“As of 2024 the standard is Next.js Server Actions + tRPC.” Fetch with Server Components, update with Server Actions, share types with tRPC. With this triad, “BFF as a separate layer” can be implemented without consciousness of it. Traditional separate-server BFFs are considered only when there are 5+ microservices and multi-team parallel development.

BFF is the entrance lobby. The moment you bring in business logic, it becomes a second monolith.

AI-era perspective

When AI-driven development is the premise, BFF concentrates value on “promoting AI understanding through type sharing.” Compositions where types are shared between frontend and backend - like tRPC and Next.js Server Actions - let AI accurately grasp “this function returns this type,” dramatically reducing bugs in generated code. Conversely, when REST’s custom schemas split types, AI writes by guessing and hallucinates.

Favored in the AI eraDisfavored in the AI era
tRPC (full type sharing)Hand-written REST + dual-managed type definitions
Next.js Server Actions (self-contained in one file)BFF in a separate repo, scattered specs
Validation via Zod schemasHand-written string verification
Same repo with FE/BE coexistingSeparated repos, distant specs

Because the range of code AI handles is decided by “context visible at once,” the composition where FE/BFF/BE share the same types in the same repo is the most understandable for AI. The tRPC + Next.js + Zod stack is a composition being re-evaluated in the AI era.

Common misconceptions

  • It’s fine to write business logic in BFF - the moment you start, bloating begins, and in a year it becomes the main system. The rule is to keep BFF thin
  • Even at small scale, putting BFF in is more modern - over-design for personal dev or mid-size SaaS. Next.js API Routes / Server Actions are enough
  • BFF and GraphQL are the same - similar problems, opposite philosophies. BFF for few clients, GraphQL for many clients with free queries
  • JWT in the browser keeps things simple - one XSS shot. BFF + httpOnly Cookie is the safe modern composition

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)

How to make the final call

The core of BFF is “keeping it thin” and “deciding adoption by working backward from scale.” BFF is a layer dedicated to screen-optimized formatting, aggregation, and auth proxying - making it carry business logic duplicates with core services and breaks down.

A standalone BFF server is over-design for personal dev or mid-size SaaS - building a thin BFF layer with Next.js API Routes or Server Actions inside the framework is enough. A full BFF is needed only “when microservices + diverse clients (Web/Mobile/TV) are present together,” and even then, change costs balloon if you don’t preserve the principles of “frontend team ownership, same repo as the frontend.”

The decisive axis is “type sharing + same repo.” tRPC + Next.js + Zod is the strongest composition where types are shared by all three of frontend, backend, and AI - AI accurately grasps “this function returns this type,” and hallucinations plummet. When REST + custom schemas split types, AI can only write by guessing, and spec-mismatch accidents between frontend and backend become more frequent.

Furthermore, the “BFF + httpOnly Cookie” auth composition has effectively become the modern Web-auth standard, since it never exposes JWTs to the browser and maximizes XSS resistance. BFF is “a mechanism translating organizational responsibility-distribution into technology, more than just a technical pattern” - operations that respect frontend team ownership are the key to success.

The selection priorities, summarized:

  1. Judge necessity from scale (small scale: not needed; microservices: required)
  2. Prefer framework built-ins (Next.js API Routes / Server Actions)
  3. Type sharing + same repo (tRPC + Zod is strongest)
  4. Keep BFF thin (business logic stays in core services)

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.