Getting Started with Next.js 16

By Pablo Fernandez2 min read

Next.js 16 landed in late 2025, and most of what changed is about caching and compilation moving from things you configure to things that happen by default. This is a tour of the parts I actually reached for when I moved this site onto it.

The "use cache" directive#

The old story for caching a Server Component was a mix of revalidateTag, unstable_cache, and a lot of guessing about what was static. Next.js 16 turns it into a directive you put at the top of a function. Everything the function returns becomes a cache boundary you can reason about.

TSXcomponents/stats.tsx
React TSX
"use cache"
 
async function UserStats({ userId }: { userId: string }) {
  const stats = await db.stats.findUnique({ where: { userId } });
 
  return (
    <div className="grid grid-cols-3 gap-4">
      <div>Posts: {stats.posts}</div>
      <div>Likes: {stats.likes}</div>
      {/* This subtree is cached on its own */}
    </div>
  );
}

The win is that the caching lives next to the code it caches, so you can see which parts of the tree are static and which are dynamic without cross-referencing a config file.

Turbopack is the default bundler#

Turbopack is now the default for both development and production, not an opt-in flag. In day-to-day work the thing you feel is Fast Refresh: edits show up fast enough that you stop noticing the compile step, which is the whole point of a dev loop. The build logs also got more useful, with a breakdown of where compile time goes.

The React Compiler, on by default#

New projects ship with the React Compiler enabled. It handles the memoization you used to write by hand, so useMemo and useCallback mostly leave your code. You describe the render and the compiler works out what can be skipped. The practical effect is fewer dependency arrays to get wrong.

TSXcomponents/search.tsx
React TSX
// No manual useMemo: the compiler decides what to skip.
export function SearchResults({ items, filter }) {
  const filtered = items.filter(item => item.name.includes(filter));
 
  return (
    <ul>
      {filtered.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

Starting a project#

Creating an app is one command, and Turbopack plus the React Compiler are the defaults when you accept the prompts:

$_
Bash
bun create next-app@latest

proxy.ts in place of middleware#

Next.js 16 renames the middleware.ts file to proxy.ts. The export is a plain function that runs before a request is routed, which is where you do redirects, auth checks, and rewrites. Here is the same "send unauthenticated users to the login page" guard I use on this site:

TSproxy.ts
TypeScript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl
 
  if (pathname.startsWith('/dashboard') && !request.cookies.get('access_token')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
 
  return NextResponse.next()
}
 
export const config = {
  matcher: ['/dashboard/:path*'],
}

The matcher decides which paths the function even runs on, so an ordinary page request pays nothing.

View Transitions#

Next.js 16 sits on React 19.2 and exposes the browser's View Transitions API, so page-to-page navigations can animate instead of cutting. You opt a transition in where you want it rather than wiring an animation library through the router.

Where it leaves you#

Most of the release pulls in the same direction: the caching and memoization you used to hand-tune are now defaults you occasionally override, not machinery you assemble. If you are upgrading, the two changes to plan for are the use cache model and the middleware.ts to proxy.ts rename. The rest you mostly get for free by taking the new defaults.

Related articles

Giving an Inbox to Each Agent

Password resets, invoices, and support replies still arrive by email, and my agents live on HTTP. Mailingest is the Rust mail platform I built so that an inbox costs one API call: an address whose mail shows up as a signed webhook.

Building a Better WhatsApp SDK

whatsapp-web.js runs a whole headless Chromium to act like a phone. The other door skips the browser and speaks WhatsApp's native protocol over a plain WebSocket, then hands you a raw socket and a firehose of events. whatsweb is the thin layer I built to make that socket feel like a bot.

The Distance From a Message to Your Code

The same bot, a user's message answered by code I wrote, costs a handful of lines on Telegram, a gateway and a bag of intents on Discord, and a whole headless browser pretending to be a phone on WhatsApp. Four libraries in, the pattern was clear, and the hard part was never the bot.