Next.js can serve API routes and Server Actions, and many apps outgrow that once they need queues, cron, long-running work or a second client. How far the built-in backend goes, the signs it is time for a separate backend, and how to connect one with a typed client.
Next.js Route Handlers serve HTTP endpoints from the app directory, Server Actions run mutations from components, and server components query databases directly, so for many products the first version never needs a separate API.
The question comes up once the app has work that does not fit a request-response cycle in a frontend framework: a job that runs every night, an email that should go out after a payment webhook, a mobile app that needs the same endpoints, or a second team that owns the data. This guide covers what the Next.js backend handles well, the signs it is time to split one out, and how to connect a separate TypeScript backend with a typed client.
Route Handlers export functions named after HTTP methods from a route.ts file and use the Web Request and Response APIs. They suit webhooks, small JSON APIs for your own frontend, OAuth callbacks and proxying to third-party APIs:
// app/api/todos/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const todos = [{ id: 1, title: "Ship the launch post", done: false }];
return NextResponse.json(todos);
}
Server Actions remove the endpoint entirely for mutations from your own UI: a form posts to an async function that runs on the server and can revalidate the page. Together with server components that read data directly, this covers CRUD apps, dashboards and content sites without a separate API layer.
Next.js is a rendering framework usually deployed as serverless functions, which limits it in these ways:
after() for small follow-up work, and retries, queues and dead-letter handling are outside its scope.When two or three of these apply, a separate backend with Next.js as its client is usually simpler than stretching the frontend framework.
Encore is a tool that works alongside your framework for TypeScript and Go, with a platform that provisions and deploys into your own AWS or GCP account. The backend is TypeScript services with typed endpoints, and databases, Pub/Sub, cron jobs and buckets are declared in the same code, so the parts Next.js leaves out are part of the backend from the start. Next.js stays on whatever host you use for the frontend, as the frontend hosting docs describe.

The Next.js starter contains both the Encore backend and the Next.js frontend in one repo:
Run in your terminal to get started locally.
A backend service with an endpoint and a database:
// api/todo/encore.service.ts
import { Service } from "encore.dev/service";
export default new Service("todo");
// api/todo/todo.ts
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
const db = new SQLDatabase("todo", { migrations: "./migrations" });
interface Todo {
id: number;
title: string;
done: boolean;
}
export const list = api(
{ expose: true, method: "GET", path: "/todos" },
async (): Promise<{ todos: Todo[] }> => {
const todos: Todo[] = [];
for await (const row of db.query<Todo>`SELECT id, title, done FROM todo ORDER BY id`) {
todos.push(row);
}
return { todos };
},
);
-- api/todo/migrations/1_create_todo.up.sql
CREATE TABLE todo (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
done BOOLEAN NOT NULL DEFAULT false
);
Generate a typed client from the running backend, then call it from a server component in Next.js:
// app/todos/page.tsx
import Client, { Environment, Local } from "../lib/client";
const encoreEnv = process.env.ENCORE_ENVIRONMENT;
const client = new Client(
process.env.NODE_ENV === "development" || !encoreEnv
? Local
: Environment(encoreEnv),
);
export default async function TodosPage() {
const { todos } = await client.todo.list();
return (
<ul>
{todos.map((t) => (
<li key={t.id}>{t.done ? "✓" : "○"} {t.title}</li>
))}
</ul>
);
}
The client carries the request and response types, so renaming a field in the backend breaks the Next.js build instead of failing at runtime. Regenerating it is covered in the request client docs and client generation. encore run starts Postgres locally, and on deploy Encore provisions the database in your AWS or GCP account. Browser calls from another origin are allowed through the global_cors setting in encore.app, as described in the CORS docs.
For AI coding agents the split helps: an agent changing an endpoint regenerates the client and gets type errors in the exact Next.js files that need updating, across both halves of the repo.
Best fit: Next.js teams whose backend needs queues, cron, multiple clients or its own deploy cycle, and who want it in TypeScript with types shared to the frontend.
Environment("staging") or a production URL through configuration, and keep Local for development.Yes, for a lot of apps. Route Handlers serve HTTP endpoints from the app directory and Server Actions run server-side mutations called from components. Next.js is weaker at background jobs, queues, scheduled work, long-lived connections and serving several clients, which is when teams add a separate backend.
Route Handlers are HTTP endpoints defined in route.ts files that export functions such as GET and POST, callable by any client. Server Actions are async functions marked with "use server" that React components call directly, mainly for form submissions and mutations from your own UI.
Use one when the backend has its own lifecycle: a mobile app or partners call the same API, work runs in the background or on a schedule, services communicate through queues, or the database and infrastructure are owned by a backend team. Keep Next.js for rendering and the UI-facing data fetching.
With Encore, define endpoints with api() from encore.dev/api, then run encore gen client --output=./app/lib/client.ts --env=local to generate a typed client. Next.js server components and Server Actions import the client and call endpoints such as client.todo.list() with full request and response types.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.