How to use Next.js as a backend for your frontend Next.js

Next.js supports the Backend for Frontend (BFF) pattern, allowing you to create public endpoints that handle HTTP requests and return any content type. This article explains how to use Route Handlers, Proxy, and API Routes to build a secure and flexible backend layer for your frontend, including examples for data transformation, request validation, and content delivery.

Backend for FrontendRoute HandlerProxyNextRequestNextResponse

~3 min read • Updated Oct 26, 2025

Introduction


Next.js supports the Backend for Frontend (BFF) pattern, allowing you to build public-facing endpoints that handle HTTP requests and return any content type—not just HTML. These endpoints can access external data sources, perform mutations, and serve as a lightweight API layer.


Getting Started


To scaffold a new project with backend capabilities:

npx create-next-app@latest --api

This includes an example route.ts file in the app/ directory.


Route Handlers


Route Handlers are public HTTP endpoints. You can define them using route.ts or route.js inside the app directory:

// app/api/route.ts
export function GET(request: Request) {
  return new Response('Hello from backend')
}

Error Handling


export async function POST(request: Request) {
  try {
    await submit(request)
    return new Response(null, { status: 204 })
  } catch (reason) {
    const message = reason instanceof Error ? reason.message : 'Unexpected error'
    return new Response(message, { status: 500 })
  }
}

Tip: Avoid exposing sensitive error messages to the client.


Serving Non-UI Content


Route Handlers can serve JSON, XML, images, files, and plain text. Common endpoint conventions include:

  • sitemap.xml
  • manifest.json
  • robots.txt
  • rss.xml

Example: serving an RSS feed:

// app/rss.xml/route.ts
export async function GET(request: Request) {
  const rssData = await fetchRSS()
  const rssFeed = `<rss>...</rss>`
  return new Response(rssFeed, {
    headers: { 'content-type': 'application/xml' },
  })
}

Reading Request Payloads


Use request.json(), request.formData(), or request.text() to read the body:

export async function POST(request: Request) {
  const data = await request.json()
  return Response.json({ data })
}

Validating Input


export async function POST(request: Request) {
  const formData = await request.formData()
  const email = formData.get('email')
  const contents = formData.get('contents')

  try {
    await validateInputs({ email, contents })
    const info = await sendMail({ email, contents })
    return Response.json({ messageId: info.messageId })
  } catch (reason) {
    return new Response('Error', { status: 500 })
  }
}

Cloning Requests


You can only read the request body once. Clone the request if needed:

const cloned = request.clone()
await cloned.body()

Data Transformation


Route Handlers can transform and aggregate data from multiple sources:

export async function POST(request: Request) {
  const body = await request.json()
  const params = new URLSearchParams({ lat: body.lat, lng: body.lng })
  const response = await fetch(`${weatherEndpoint}?${params}`)
  const raw = await response.text()
  const parsed = parseWeatherData(raw)
  return new Response(parsed, { status: 200 })
}

Proxying Requests


Use Route Handlers to proxy requests to external backends:

// app/api/[...slug]/route.ts
export async function POST(request: Request, { params }) {
  const isValid = await isValidRequest(request.clone())
  if (!isValid) return new Response(null, { status: 400 })

  const pathname = params.slug.join('/')
  const proxyURL = new URL(pathname, 'https://nextjs.org')
  const proxyRequest = new Request(proxyURL, request)
  return fetch(proxyRequest)
}

NextRequest and NextResponse


Next.js extends the Web APIs with NextRequest and NextResponse:

  • NextRequest: includes nextUrl for parsed pathname and query params
  • NextResponse: includes helpers like json(), redirect(), and rewrite()

export async function GET(request: NextRequest) {
  const url = request.nextUrl
  if (url.searchParams.get('redirect')) {
    return NextResponse.redirect(new URL('/', request.url))
  }
  return NextResponse.json({ pathname: url.pathname })
}

Conclusion


Next.js makes it easy to build a backend layer for your frontend. With Route Handlers, Proxy, and extended Web APIs, you can serve content, process data, and integrate with external systems—all within your app directory.


Written & researched by Dr. Shahin Siami