# Next.js

## What it is

Next.js is a framework that wraps React, the library for building user interfaces out of components,
and supplies the parts React leaves to each application: routing, rendering on the server, data
fetching and a build that is configured for you. The package and its command are both called `next`,
as in `next dev` and `next build`.

It was first released in 2016, in [its authors' words](https://nextjs.org/blog/next-13-4) “to
provide an easy way to server-render React applications”. The early design rested on a few
principles: zero setup, the filesystem as an API, so that a file in a `pages/` folder became a
route, automatic server rendering and code splitting, and data fetching left to the developer. By
2023 the release notes credited over 2,600 developers alongside a core team at Vercel.

Next.js 13, on October 25, 2022, added an `app` directory in beta, built on
[React Server Components](https://react.dev/reference/rsc/server-components), with nested layouts
and streaming. Next.js 13.4, on May 4, 2023, declared this App Router stable, and the documentation
has described two routers ever since. The Pages Router is “the original router, still supported and
being improved”. They differ even in which React they run: the App Router ships React canary
releases built in, while the Pages Router uses whatever version the project's `package.json` names.
In the App Router, pages and layouts render on the server unless a file opts into the browser with a
`"use client"` directive.

Next.js 16 arrived on October 21, 2025. It made Turbopack the default bundler, renamed the
`middleware.ts` file to `proxy.ts`, and introduced Cache Components, a model in which a `"use
cache"` directive marks what may be cached and everything else is rendered at request time. Next.js
16.4, on October 6, 2026, recommends Cache Components for every app, switches them on for new
projects made with `create-next-app`, and says they “will become the default in Next.js 17”.

## Why it matters

The decision Next.js answers is whether to let a framework own the routing, rendering and server of
a React application, or to assemble those pieces yourself and keep the choices.

What it offers is convention in place of configuration. Routes come from files, bundlers and
compilers are set up automatically, and each route can be built ahead of time, rendered on the
server for each request or rendered in the browser. Server Components keep data access and secrets
on the server and send less JavaScript to the browser. For an application that mixes public pages
with a signed-in area and its own backend, that per-route choice is the main attraction.

The first cost is a server, running Node.js 20.9 or later for Next.js 16. Setting `output: 'export'`
writes one HTML file per route for any static web server, and the guide pitches it as a way to start
static and add server features later. Its list of what needs a Node.js server, though, covers much
of what sets Next.js apart: Proxy, Server Actions, cookies, rewrites, redirects, headers,
Incremental Static Regeneration and image optimisation with the default loader.

The second cost is pace. Four feature releases followed 16.0 within a year, and the caching model is
about to change its default again. The 16.4 notes say Cache Components replace “the implicit caching
behaviors of the previous App Router versions”, so code written for Next.js 13 to 15 caches by
different rules. Tutorials found online are split between the two routers as well.

The third cost is that upgrades are security work. On December 3, 2025, the project published an
advisory for CVE-2025-66478, a critical vulnerability in the React Server Components protocol,
telling users to upgrade to patched versions immediately, and the blog carried three more security
posts between September 30 and October 14, 2026. A team that adopts Next.js signs up to follow its
releases closely. A team publishing documents that read the same for every visitor pays for a server
model it hardly uses.

## What it looks like in practice

The company's Next.js 16 frontend
[renders each surface its own way](/portfolio/built-the-next-js-16-frontend-with-deliberate-73/),
which is the per-route choice put to work. Marketing and public pages are statically generated, the
genuinely interactive parts stay client-rendered, and on the authenticated dashboard and the big
directory listings a factory, `createPrefetchedServerPage`, fetches the page's data on the server
and hands it to the client already populated. Pages move onto the factory one at a time, and the
page is candid that the rest of the authenticated app is still client-rendered and queued behind it:
“a migration with a working mechanism and an obvious next page, rather than a finished sweep.” The
App Router was designed for that kind of move; the 13.4 release notes describe adopting it
incrementally.

A [layered test suite](/portfolio/built-a-layered-automated-test-suite-across-four-layers-94/) gives
a Next.js frontend a layer of its own: 543 Vitest and Playwright specs, including 48 end-to-end
files and 30 browser specs, beside separate layers for the Go API and the database. Each layer is
checked where its behaviour lives rather than everything through a browser, which matters in a
framework whose component tree runs partly on the server and partly in the browser.

When the company
[hardened an application](/portfolio/hardened-the-application-with-nonce-based-csp-hsts-77/), the
browser-facing protections went into `proxy.ts`, the renamed middleware. It sets a
Content-Security-Policy with a per-request nonce and `strict-dynamic`, plus HSTS and SameSite
cookies. Authorisation lives elsewhere: entitlements, covering tier, role, organisation and ship
scoping, are re-checked on the server on every request, and the frontend's gates count as UX only.
That split matches the Proxy reference, which calls the feature a last resort and tells developers
to verify authorisation “inside each Server Function rather than relying on Proxy alone”.

## Where it goes wrong

**After a sign-in redirect is added, the login page loads without its styles.** Without a `matcher`,
Proxy runs on every request, including built files under `_next/static`, optimised images under
`_next/image` and everything in `public/`, so a redirect meant for pages also catches the CSS,
JavaScript and images the login page needs. Give Proxy a matcher that skips those paths:

```ts
// proxy.ts
export const config = {
    matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
```

Files from `public/` share no prefix, so add their paths as well.

**A form action works for a signed-out user on a route that looks protected.** Server Functions have
no routes of their own: they arrive as POST requests to the page where they are used, so a matcher
that skips a path skips the functions there as well. The Proxy reference warns that changing the
matcher, or moving a function to another route during a refactor, “can silently remove Proxy
coverage”. Check the session and the user's permissions inside every Server Function, and treat
Proxy as an outer layer, never the only one.

**A user saves a change and the page still shows the old value.** In Next.js 16, `revalidateTag(tag,
'max')` gives tagged entries stale-while-revalidate behaviour: the cached copy keeps being served
while a fresh one is fetched, which suits content that can tolerate eventual consistency and
surprises the person who just pressed Save. Call `updateTag(tag)` from the Server Action that made
the change; it expires the entry and reads fresh data within the same request. The old one-argument
`revalidateTag(tag)` is deprecated and is now a TypeScript error.

**The upgrade codemod finishes and the build still fails on `params` or `cookies()`.** Next.js 15
made the request APIs asynchronous with temporary synchronous compatibility, and Next.js 16 removes
it. The `upgrade` codemod does not run every migration, so also run
`npx @next/codemod@canary next-async-request-api .`, then `npx next typegen` to generate the
`PageProps`, `LayoutProps` and `RouteContext` helpers. The same upgrade removes `next lint` and
stops `next build` from linting, so add ESLint or Biome as a step of its own.

## Further reading

- [Next.js Docs](https://nextjs.org/docs) — the official documentation, with a switch between the
  App Router and Pages Router versions of every page.
- [Next.js 16](https://nextjs.org/blog/next-16) — the release that brought Cache Components,
  `proxy.ts` and Turbopack as the default bundler, with its breaking changes.
- [How to upgrade to version 16](https://nextjs.org/docs/app/guides/upgrading/version-16) — the
  codemods, the version requirements and every removed or renamed API.
- [proxy.js](https://nextjs.org/docs/app/api-reference/file-conventions/proxy) — matchers, where
  Proxy sits in the request order and why middleware was renamed.
- [Next.js 16.4](https://nextjs.org/blog/next-16-4) — the case for Cache Components and the plan to
  make them the default in Next.js 17.
- [How to create a static export](https://nextjs.org/docs/app/guides/static-exports) — which
  features need a server, and what `output: 'export'` writes.

<https://engineer.company/glossary/nextjs/>
