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.”
Other articles in this category
“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.
| Responsibility | Content |
|---|---|
| API aggregation | Gather from multiple microservices into one screen’s worth |
| Data shaping | Convert to a screen-friendly format, drop unneeded fields |
| Auth/session | Token management, cookie-ization, hiding secret keys |
| Cache | HTTP cache for API responses, edge cache |
| Rate limit | DDoS 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.
| Pattern | Notes |
|---|---|
| Next.js API Routes / Server Actions | Most prevalent in the React family |
| Nuxt Server Routes | Equivalent option in the Vue family |
| Remix Loader/Action | Web-standards-focused design philosophy |
| tRPC | Godlike dev experience via TypeScript type sharing |
| Hono + Cloudflare Workers | Edge BFF, low latency |
| Dedicated Express/Fastify | Lightweight, 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.
| Viewpoint | BFF | GraphQL |
|---|---|---|
| Implementation | Endpoints per screen | Single endpoint |
| Learning cost | Light | Heavy (design/operation hard) |
| Flexibility | Server-side fixed shaping | Client queries freely |
| Cache | Easy via HTTP standards | Requires 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.
| Phase | Scale/architecture | BFF treatment | Implementation method |
|---|---|---|---|
| 1. Personal/MVP | ~1,000 MAU | Unneeded (direct API calls) | fetch + useState |
| 2. Early startup | ~10,000 MAU, single backend | Thin BFF (FW built-in) | Next.js API Routes / Server Actions |
| 3. Mid-size SaaS | 10,000-100,000 MAU, multiple microservices | Aggregation BFF | tRPC + Next.js |
| 4. Large | 100,000+ MAU, multi-team | Dedicated BFF layer (per screen) | Dedicated Express/Fastify + gRPC |
| 5. Multi-client | Web + Mobile + TV | Per-client BFF | Each client dedicated |
| 6. Edge-distribution priority | Global, low-latency-focused | Edge BFF | Hono + 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 move | Why it’s bad |
|---|---|
| Write business logic in BFF | Duplicates with core service in a year. Don’t put a vault in the entrance lobby |
| BFF owned by the backend team | Every screen change requires cross-team coordination. Frontend team ownership is the rule |
| Separate repos for BFF and FE | Screen change requires 2 PRs, accidents at sync timing |
| No cache strategy in BFF | Origin 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 types | Types are split, dual-managed type definitions. Fully share via tRPC |
| No timeout in BFF | Backend failures topple the BFF, avalanche cascade |
| Update without optimistic locking in BFF | Concurrent updates cause lost updates, data corruption |
| Letting non-frontend clients hit the BFF too | The point of BFF disappears. Maintain use-case specialization |
| Unify multiple microfrontends on one BFF | Team 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 era | Disfavored 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 schemas | Hand-written string verification |
| Same repo with FE/BE coexisting | Separated 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:
- Judge necessity from scale (small scale: not needed; microservices: required)
- Prefer framework built-ins (Next.js API Routes / Server Actions)
- Type sharing + same repo (tRPC + Zod is strongest)
- 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.
📚 Series: Architecture Crash Course for the Generative-AI Era (36/89)