zgba Network

Using Next.js as a Backend for Frontend (BFF)

When building a modern web application, the frontend often needs to communicate with multiple backend services: Browser β”‚ β”œβ”€β”€ Auth Service β”œβ”€β”€ Product Service β”œβ”€β”€ Order Service └── Notification Service This works, but it makes the frontend tightly coupled to the backend architecture. A better approach in some systems is to introduce a Backend for Frontend (BFF). Browser β”‚ β–Ό Next.js BFF β”‚ β”œβ”€β”€ Auth Service β”œβ”€β”€ Product Service β”œβ”€β”€ Order Service └── Notification Service What is a BFF? A BFF is a backend layer specifically designed for a particular frontend. Instead of exposing all backend services directly to the browser, the frontend communicates with the BFF, and the BFF communicates with the internal services. This gives us a place to handle: Authentication Authorization Request validation API aggregation Response transformation Caching Hiding internal service URLs Next.js as a BFF Next.js can implement a BFF using Route Handlers. For example: app/ └── api/ └── products/ └── route.ts // app/api/products/route.ts export async function GET() { const response = await fetch( {process.env.PRODUCT_SERVICE_URL}/products ); if (!response.ok) { return Response.json( { message: β€œFailed to fetch products” }, { status: 500 } ); } const products = await response.json(); return Response.json(products); } Now the browser calls: GET /api/products Instead of directly calling: GET http://product-service:3002/products The architecture becomes: Browser β”‚ β”‚ GET /api/products β–Ό Next.js BFF β”‚ β”‚ GET /products β–Ό Product Service The internal service URL remains server-side. API Aggregation One of the most useful BFF features is aggregation. Imagine a dashboard needs data from four different services: User Service Order Service Notification Service Recommendation Service Instead of making four requests from the browser: Browser β”œβ”€β”€ GET /users/me β”œβ”€β”€ GET /orders β”œβ”€β”€ GET /notifications └── GET /recommendations The BFF can expose a single endpoint: GET /api/dashboard And fetch the data in parallel: // app/api/dashboard/route.ts export async function GET() { const [ user, orders, notifications, recommendations, ] = await Promise.all([ fetch({process.env.USER_SERVICE_URL}/me) .then(res => res.json()), fetch({process.env.ORDER_SERVICE_URL}/orders) .then(res => res.json()), fetch({process.env.NOTIFICATION_SERVICE_URL}/notifications) .then(res => res.json()), fetch({process.env.RECOMMENDATION_SERVICE_URL}/recommendations) .then(res => res.json()), ]); return Response.json({ user, orders, notifications, recommendations, }); } Now the browser only needs: GET /api/dashboard The BFF handles the complexity behind the scenes. Response Transformation The BFF can also transform backend responses into a format that is better suited for the frontend. For example, a backend might return: { β€œid”: 15, β€œfirst_name”: β€œAbanoub”, β€œlast_name”: β€œKerols”, β€œinternal_role_id”: 7 } But the frontend may only need: { β€œid”: 15, β€œname”: β€œAbanoub Kerols” } The BFF can perform this transformation: return Response.json({ id: user.id, name: {user.first_name} {user.last_name}, }); This prevents the frontend from becoming dependent on internal backend models. Authentication A BFF can also be useful for authentication. For example: Browser β”‚ β”‚ HttpOnly Cookie β–Ό Next.js BFF β”‚ β”‚ Authorization: Bearer β–Ό Backend Service The BFF can read the server-side authentication state and attach the appropriate credentials when calling internal services. This can help keep sensitive tokens away from client-side JavaScript. The Important Architectural Boundary Using Next.js as a BFF does not mean moving all business logic into Next.js. A good separation is: Next.js β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ UI β”‚ β”‚ BFF β”‚ β”‚ Authenticationβ”‚ β”‚ Aggregation β”‚ β”‚ Transformationβ”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό Backend Services β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Business Logicβ”‚ β”‚ Domain Rules β”‚ β”‚ Data Access β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ The BFF should mainly adapt backend capabilities to the needs of the frontend, while important domain logic remains in the backend services. Final Architecture Browser β”‚ β”‚ HTTPS β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Next.js β”‚ β”‚ BFF β”‚ β”‚ β”‚ β”‚ Auth β”‚ β”‚ Validation β”‚ β”‚ Aggregation β”‚ β”‚ Transformation β”‚ β”‚ Caching β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–Ό β–Ό β–Ό Auth API Product API Order API β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό Database Database Database The key idea Next.js doesn’t have to replace your backend. It can become the backend layer specifically designed for your frontend. That’s the core idea behind using Next.js as a Backend for Frontend (BFF).

View original article