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