Introduction
The Backend for Frontend (BFF) is an architectural pattern that involves creating a dedicated backend layer for each client type (web, mobile). This approach aggregates data from multiple microservices, manages authentication, and reduces network calls from the frontend. In 2026, with the rise of distributed applications, the BFF becomes essential for optimizing performance and maintainability. This tutorial shows you how to implement a BFF with Next.js in a concrete and professional way.
Prerequisites
- Node.js 20+
- Intermediate knowledge of TypeScript and Next.js
- npm or pnpm installed
- Vercel account or similar hosting provider
Initializing the Next.js Project
npx create-next-app@latest bff-demo --yes
cd bff-demo
npm install axios zodThis command creates a modern Next.js project and installs axios for HTTP requests as well as zod for data validation. These dependencies are essential for a robust BFF.
Creating the Main BFF Route
import { NextResponse } from 'next/server';
import axios from 'axios';
import { z } from 'zod';
const DashboardSchema = z.object({
userId: z.string(),
});
export async function POST(request: Request) {
const body = await request.json();
const { userId } = DashboardSchema.parse(body);
const [user, orders] = await Promise.all([
axios.get(`https://api.users.com/${userId}`),
axios.get(`https://api.orders.com?userId=${userId}`),
]);
return NextResponse.json({
user: user.data,
orders: orders.data,
});
}This Next.js API route acts as the BFF. It aggregates data from two external services in a single request, reducing client-side latency. Zod ensures strict input validation.
Adding the Authentication Layer
import { NextResponse } from 'next/server';
// ... previous imports
export async function POST(request: Request) {
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Continue with aggregation logic...
}We add JWT token verification directly in the BFF. This centralizes authentication logic and avoids implementing it on every microservice.
Cache and Error Management
import { unstable_cache } from 'next/cache';
export const getCachedDashboard = unstable_cache(
async (userId: string) => {
// BFF call logic here
return { user: {}, orders: [] };
},
['dashboard'],
{ revalidate: 60, tags: ['dashboard'] }
);Next.js unstable_cache allows caching BFF responses for 60 seconds. This reduces load on backend services and improves performance.
Calling from the Client Component
'use client';
import { useEffect, useState } from 'react';
export default function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/bff/dashboard', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: '123' }),
})
.then(res => res.json())
.then(setData);
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}The React component only calls the BFF. This simplifies the frontend and hides the complexity of underlying microservices.
Best Practices
- Always validate inputs with Zod or a similar schema
- Centralize authentication and authorization logic in the BFF
- Use Next.js cache for low-volatility data
- Monitor external service latencies
- Document each BFF endpoint with OpenAPI
Common Errors to Avoid
- Forgetting async error handling with try/catch
- Exposing microservice errors directly to the client
- Not configuring CORS headers correctly
- Creating oversized BFFs that become god objects
Going Further
Deepen your knowledge of BFF architecture and modern patterns with our expert courses: https://learni-group.com/formations