What is Next.js? Frontend or backend, and when to use it
Next.js is a React framework for building full-stack web applications: you write the UI as React components, and Next.js handles routing, server rendering, bundling and APIs. “Is Next.js frontend or backend?” has a short answer: both, in one project. This page shows it with a small app that was run, then says when to use it.
The code on this page is an app that runs: 8/8 checks passed on 2026-10-31. Versions run: next@16.4.3, react@19.3.1, typescript@7.0.2, Node.js v26.0.0.
What Next.js is
The Next.js docs define it: “Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations.” It also configures lower-level tools like bundlers and compilers for you.
React alone is a UI library; it does not decide which URL opens which page, where data comes from, or how code is bundled. react.dev recommends starting a new app with a framework and lists Next.js (App Router) first among full-stack frameworks. Next.js is open source (MIT licence) and maintained by Vercel.
Next.js has two routers: the App Router (newer, supports Server Components) and the Pages Router (the original, still supported). New projects should use the App Router; every example here does. The latest stable release on npm is 16.4.3, as of 31 Oct 2026.
The “backend” part: a Server Component reads data on the server
In the App Router every page and layout is a Server Component by default: it runs on the server and the browser receives HTML. So it can read files, query a database and use API keys without exposing them to the browser.
The function below reads a JSON file from disk, which a browser cannot do. import 'server-only' fails the build if this file ever ends up in browser code. Checked: no file under .next/static (the code sent to browsers) contains it.
import 'server-only'
import { readFile } from 'node:fs/promises'
export type Post = { id: number; title: string }
// Runs only on the server: it reads a file from disk, which a browser cannot do.
export async function getPosts(): Promise<Post[]> {
const raw = await readFile(process.cwd() + '/data/posts.json', 'utf8')
return JSON.parse(raw)
}import { getPosts } from './lib/posts'
import LikeButton from './ui/like-button'
// A Server Component (the default): it runs on the server, and the browser gets HTML.
export default async function Page() {
const posts = await getPosts()
return (
<main>
<h1>Bài viết</h1>
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
<LikeButton />
</main>
)
}The “frontend” part: a Client Component runs in the browser
When you need state, click handlers, useEffect or browser APIs (localStorage, window), mark the file 'use client'. Next.js still pre-renders it to HTML (the button shows “Lượt thích: 0” even with JavaScript off), then sends its code to the browser so the button works.
The Next.js docs' advice: keep most of the page as Server Components and make only the interactive pieces Client Components, so less JavaScript is sent.
'use client'
import { useState } from 'react'
// A Client Component: its code is sent to the browser, so it can hold state and handle clicks.
export default function LikeButton() {
const [likes, setLikes] = useState(0)
return <button onClick={() => setLikes(likes + 1)}>Lượt thích: {likes}</button>
}An API in the same project: Route Handlers
A route.ts file in the app directory is an HTTP endpoint built on the Web Request/Response APIs. GET /api/posts below answers JSON and shares getPosts() with the page. A mobile app or another system can call it like any API.
import { getPosts } from '../../lib/posts'
// A Route Handler: a backend endpoint in the same project. GET /api/posts answers JSON.
export async function GET() {
return Response.json(await getPosts())
}At build time or per request
Next.js decides when each route renders. The / page reads nothing from the request, so it is rendered once during next build; Route Handlers are not cached by default, so they run on every request. Tested: after editing data/posts.json post-build, / still serves the old list while /api/posts serves the new one.
This trips up newcomers: a static page does not update when the data changes. To follow the data, use revalidation, time-limited caching, or per-request rendering — each gets its own guide.
The cost: JavaScript sent to the browser
The sample page is one list and one button, yet the browser still loads the React and Next.js runtime. The measured figure is under “Checked” below (the JavaScript line for /, gzipped). For a pure content page that is a cost worth weighing; in an interactive app it is shared across the whole app.
When to use Next.js
Your team knows React, and the site needs both pre-rendered content (good for SEO, fast first paint) and interactive parts.
You want the UI and a small API in one project, deployed once.
You want to start static and add server features later: per react.dev, the recommended frameworks can run as static sites or SPAs and turn on server rendering per route, without a rewrite.
When to consider something else
You only need static pages and have no Node.js server: Next.js can export a static site (output: 'export'), but loses the server features — the docs list Server Actions, Proxy, Incremental Static Regeneration, image optimization with the default loader, cookies, rewrites, redirects and headers. Compare it with a purely static tool before choosing.
A large backend serving many kinds of client and running background jobs: Next.js ships no database, ORM or queue — you choose them. Our view: when business logic is the main part, a separate backend (NestJS, Laravel, Django…) behind Next.js is usually easier to manage.
Your team does not use React: Next.js is a React framework. Other frameworks follow a similar model (Nuxt for Vue, SvelteKit for Svelte).
Where it runs
Per the Next.js docs: as a Node.js server (next build, then next start) or a Docker container you get every feature; a static export is limited; and adapters exist per platform (verified adapters: Vercel and Bun). Vercel is not required — a VPS with Node.js is enough.
Once you run Next.js, keep up with security patches: see the advisory list and check your site's version through the links below.
Checked
- next build: / is prerendered, /api/posts is not:
/ build time, /api/posts per request - GET / with no JavaScript: the posts read on the server are in the HTML:
200 + 3 titles - Client Component: the button is already in the HTML, at 0:
pre-rendered - Client Component: its code is in a script / loads:
in a browser chunk - server-only module (reads data/posts.json): in no file under .next/static:
0 files - Route Handler: GET /api/posts:
200 application/json, 3 posts - edit data/posts.json after the build: / keeps its build-time list, /api/posts reads the file again:
/ old list, /api/posts new list - JavaScript the browser loads for / (gzip):
172 KB in 7 files
Re-run the checks: bun scripts/guides/verify-intro.ts
Related
- Authentication in Next.js (App Router): sessions, authorization, Proxy
- Next.js security advisories
- Check the Next.js version of your site
Sources
- Next.js docs — What is Next.js?
- Next.js docs — Server and Client Components
- Next.js docs — Route Handlers
- Next.js docs — Deploying
- Next.js docs — Static Exports (unsupported features)
- react.dev — Creating a React App
- github.com/vercel/next.js (MIT licence)
- npm registry — next dist-tags
Our summary and wording; the code is rewritten from the examples in the Next.js docs. Where they differ, the Next.js docs win.