Next.js 16 — 47 Fetch Calls Uncached, Doubling Server Costs
Upgrading to Next.js 16? CPU spiked 85%, cache hits dropped to 15% due to changed fetch defaults.
- Next.js 16 ships with React 19, Partial Prerendering (stable), and Turbopack as default bundler
- Partial Prerendering combines static shell with dynamic streaming in a single response
- React 19 brings use() hook, enhanced Server Actions, and React Compiler (experimental)
- Fetch caching default changed in v15 (not v16) — from force-cache to no-store, explicit cache required in v16
- Turbopack replaces Webpack as the default dev and build bundler
- Biggest mistake: upgrading from v14 to v16 without auditing fetch — every request hits origin
Next.js 16 is like upgrading from a regular car to an electric one. The roads are the same (React), but the engine (Turbopack), the battery (Partial Prerendering), and the dashboard (caching) are completely new. Some habits from the old car (like automatic fetch caching) no longer work — you need to learn the new controls before driving.
Next.js 16 is a major release that changes how applications are built, cached, and rendered. It ships with React 19 as the minimum version, Turbopack as the default bundler, and Partial Prerendering as a stable rendering strategy.
These changes are not incremental — they alter fundamental defaults that existing applications depend on. Fetch caching behavior changed. The bundler changed. The rendering model expanded. Applications upgrading from Next.js 14 or 15 without understanding these shifts will encounter broken caching, slower builds, and unexpected rendering behavior.
Tested on Next.js 16.0.0-canary, React 19.0.0.
Why Next.js 16 Defaults to Uncached Fetch — And Why It Hurts
Next.js 16 changes the default caching behavior of fetch() from 'force-cache' to 'no-store'. This means every fetch call in a server component or route handler makes a fresh HTTP request on every render, bypassing the built-in data cache entirely. The core mechanic is simple: unless you explicitly opt in with cache: 'force-cache' or use a revalidation strategy, each request hits the origin server every time.
In practice, this flips the mental model. Previously, developers could rely on automatic deduplication and caching across requests. Now, every page load, every navigation, every re-render triggers a new fetch. For a typical page with 47 fetch calls, that's 47 uncached requests per visit. If your page gets 10,000 visits per day, that's 470,000 extra origin requests daily — directly doubling server costs if your API charges per request or your database can't handle the load.
Use this default when you are building real-time dashboards, live data feeds, or any system where stale data is unacceptable. But for most content-driven sites — blogs, e-commerce, marketing pages — you must explicitly add caching or revalidation. The default is optimized for correctness, not cost or performance. Ignoring it in production is a fast track to a surprise cloud bill.
React 19 Integration
Next.js 16 requires React 19 as the minimum version. This is not a peer dependency bump — React 19 changes how components render, how data flows, and how the compiler optimizes code.
The three most impactful React 19 features in Next.js 16 are enhanced Server Actions (stable since Next.js 14, with useActionState and improved Form handling in React 19), the use() hook for reading promises and context in conditional logic, and the React Compiler (experimental integration) that eliminates the need for useMemo and useCallback in most cases.
Server Actions replace API route handlers for form submissions and mutations. The use() hook replaces useEffect-based data fetching patterns for Server Component data. The React Compiler automatically memoizes components, removing a major source of performance bugs.
- Server Actions eliminate API route handlers for mutations — direct server functions from components
- use() hook replaces useEffect for data fetching — promises unwrap inside components
- React Compiler auto-memoizes — useMemo and useCallback become unnecessary in most cases
- Form component handles pending states — no manual loading state management
- useActionState replaces useReducer for form state with built-in error handling
Partial Prerendering (PPR)
Partial Prerendering is the most significant rendering innovation in Next.js 16. It combines static and dynamic rendering in a single response — the static shell is served instantly from the edge, while dynamic content streams: let the compiler work, then profile in via React Suspense.
PPR solves the fundamental tension between static performance and dynamic content. Traditional static generation serves fast pages but cannot show personalized data. Traditional server-side rendering shows personalized data but adds server latency to every request. PPR does both — the static parts render at build time, the dynamic parts render at request time.
The implementation requires wrapping dynamic content in Suspense boundaries. Everything outside Suspense is pre-rendered statically. Everything inside Suspense streams dynamically. Next.js automatically splits the response into static and dynamic chunks.
- Everything outside Suspense is pre-rendered statically at build time
- Everything inside Suspense is rendered dynamically at request time
- The user sees the static shell immediately — no blank page waiting for server
- Dynamic content streams in as each Suspense boundary resolves
- Edge CDN serves the static shell — sub-100ms TTFB globally
Turbopack as Default Bundler
Turbopack replaces Webpack as the default bundler in Next.js 16 for both development and production builds. This is not an experimental flag — Turbopack is the default, and Webpack is the fallback.
Turbopack is written in Rust and provides significant performance improvements over Webpack. Development server cold starts are 5-10x faster. Incremental rebuilds are near-instant. Production builds are faster and produce smaller bundles due to more aggressive tree-shaking.
The migration from Webpack to Turbopack is mostly transparent for standard Next.js applications. Custom Webpack configurations in next.config.js need to be migrated to Turbopack equivalents. Some Webpack loaders and plugins are not compatible and need replacement.
- Custom
webpack()callback in next.config.js is ignored — migrate to turbopack config - Some Webpack loaders are incompatible — check Turbopack compatibility before upgrading
- webpack-bundle-analyzer does not work with Turbopack — use next build --debug instead
- Module Federation is not supported in Turbopack — use Webpack fallback if needed
- Custom PostCSS config works but custom Webpack PostCSS loader does not
webpack() configs must be migrated to the declarative turbopack config.Caching Changes
Next.js 15 changed the default caching behavior for fetch() requests — a breaking change that causes most incidents during Next.js 16 upgrades. In Next.js 14 and earlier, fetch was cached by default (force-cache). Starting in Next.js 15, fetch uses no-store by default — every request hits the origin server unless you explicitly opt into caching.
Next.js 16 does not change the default again, but it enforces the explicit caching model and adds new APIs: the 'use cache' directive and cacheLife profiles for fine-grained, component-level control. Combined with Turbopack as the default bundler, a direct upgrade from v14 to v16 surfaces uncached fetches immediately as origin load spikes.
This remains the #1 upgrade risk because applications built on v14 relied on implicit caching. The v15+ model is predictable: you must choose force-cache, revalidate: N, or no-store for every fetch.
- Next.js 15+: fetch defaults to no-store — every fetch hits origin unless configured
- Next.js 14 and earlier: fetch defaulted to force-cache — implicit caching
- Upgrading 14→16 skips the v15 breaking change notice — audit fetches first
- Use grep to find all fetch calls without cache options before upgrading
- Next.js 16 adds 'use cache' and cacheLife — use them for component-level control
Improved Image and Font Handling
Next.js 16 improves the next/image component with automatic AVIF/WebP format selection, responsive image generation, and blur-up placeholders. The next/font module now supports variable fonts natively and provides zero-layout-shift font loading.
These improvements reduce Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS) — two Core Web Vitals metrics that directly impact SEO rankings. The image component now generates srcset and sizes attributes automatically based on the image container width.
- Set priority={true} on above-the-fold images — they load eagerly with high fetch priority
- Use sizes attribute to prevent loading oversized images on mobile
- next/font eliminates layout shift — fonts self-host with automatic subsetting
- Variable fonts reduce HTTP requests — one file covers all weights and styles
- Blur placeholder provides instant visual feedback while the image loads
Server Actions After Dark: Why Your RPC Layer Just Became an Attacker’s Playground
Server Actions in Next.js 16 aren't new — they're mandatory if you want to mutate data without a client-side waterfall. But here's what the hype pieces won't tell you: they ship as regular POST endpoints, and you've just handed every frontend user a direct line to your database mutation logic.
The framework will generate the endpoint for you. That's the clever bit. The dangerous bit is that every form action, every button that calls useTransition with a server action — it's now a public API. If you're wrapping inside a Server Action without checking the session, congratulations — you've just built an admin panel for the whole internet.db.users.delete()
Rate limiting, CSRF tokens, and server-side validation aren't optional anymore. They're the difference between 'ship fast' and 'ship a lawsuit'. Next.js 16 auto-generates the form data parsing, but it won't protect you from yourself. Treat every Server Action like a raw Express route with no middleware.
/api/actions. Add a middleware that logs every action call with IP and user ID — you'll thank yourself when the pentest report lands.CSS Modules in 16: The `.module.css` File That’s About to Break Your Production Build
CSS Modules worked fine in Next.js 14. In 16, Turbopack is the default bundler, and it handles CSS Modules differently than webpack did. The 'it just works' crowd is about to discover that their global CSS variables don't cascade into .module.css files the same way.
The problem: Turbopack compiles each CSS Module in isolation by default. If you're relying on :root variables defined in a global stylesheet leaking into your scoped module class, they won't resolve at build time. You'll see 'undefined' in production where a color variable should be. Debugging that at 2 AM is not fun.
The fix is boring but necessary: explicitly import your design tokens into every CSS Module that uses them, or use @value for shared constants. Or stop using CSS Modules entirely and switch to Tailwind — but that's a different argument. If you're keeping CSS Modules, run next build with the --debug flag before your staging deploy. Catch the 'undefined' variables before your QA team does.
export const dynamic = 'force-static' to a layout that uses CSS Modules. If the build output shows empty variable values, you've got the isolation bug. Switch to explicit imports.:root variables leaking in. Import them explicitly or use @value.Before You Even Think About `npx create-next-app`: What’s Running on Your Machine
Everybody wants to skip to the npm run dev dopamine hit. But Next.js 16 ships with Turbopack, React 19, and a stricter Node.js baseline. Forget the right Node version and your first build will fail before a single line of your code runs — but only in CI, not locally, because your laptop happens to run Node 22 while Docker runs 18.
Minimum required: Node.js 18.17 or higher. That’s non-negotiable. Turbopack needs 18.17+ for native fetch and WebSocket improvements. React 19 also drops support for older Node runtimes. If you’re still on Node 16, you’re not upgrading to Next.js 16. Period.
Also: You need pnpm 8+, yarn 4+, or npm 10+. The new dependency resolution chokes on older lockfile formats. And don’t npx create-next-app inside an existing project unless you like debugging port conflicts and rogue .next folders. Fresh directory or die.
I’ve seen three teams waste a day because someone had Node 16.10 installed via nvm and forgot to switch. Use .nvmrc from day one.
node-version: 20 in your GitHub Actions or Dockerfile. This bug hits staging, not local.node --version before you code. Save yourself a Slack apology.Local Dev Loop: Kill the Refresh Dance Before It Kills Your Flow
Next.js 16’s Turbopack dev server is fast — but only if you treat it right. Stop running next dev from your monorepo root without a turbo.json pipeline. That’s how your incremental builds turn into full recompiles. Set experimental.turbo explicitly in next.config.js or the optimizer makes pessimistic assumptions about your routes.
Second: you’re not using next dev --turbo? You’re running Webpack 5 on a framework that defaulted to Turbopack. Your dev server will be slower than your colleagues’. That’s not Next.js slowing you down — that’s you refusing to read release notes.
Third: When debugging Server Actions locally, use NODE_ENV=development next dev. Without it, error stacks get minified. Yes, even in dev mode. Turbopack does not auto-enable readable traces. I wasted two hours on a 500 that was a null reference in a server action — took me seconds in prod because the stack was mangled in dev.
Stop manually killing node_modules/.cache. Next.js 16 does partial invalidation now. Let it breathe.
"dev": "NODE_ENV=development next dev --turbo" to your package.json scripts. Then alias nd to npm run dev in your shell. Muscle memory beats turbopack flag I promise you.next dev --turbo with NODE_ENV=development or your error traces will lie to you.Next.js 14 to 16 Upgrade Broke API Caching and Doubled Server Costs
fetch() default caching from force-cache to no-store in October 2024. The team was on Next.js 14, where 47 fetch calls relied on implicit caching. They upgraded directly to Next.js 16, skipping the v15 release notes. With no explicit cache options, all 47 fetches became uncached in production. Next.js 16 didn't introduce the change — it exposed the v15 breaking change during the Turbopack-enabled build.- The fetch caching default changed in Next.js 15, not 16 — a 14→16 jump inherits this breaking change
- Implicit caching hides performance dependencies — always use explicit cache options
- Test CDN cache hit ratios in staging before any major version jump
- Turbopack in Next.js 16 actually improved performance once caching was fixed
grep -rn 'fetch(' app/ --include='*.tsx' --include='*.ts' | grep -v 'cache'Add cache: 'force-cache' or revalidate: N to each fetchKey takeaways
fetch() (introduced in v15)Common mistakes to avoid
7 patternsUpgrading straight from Next.js 14 to Next.js 16 (skipping 15)
Upgrading to Next.js 16 without auditing fetch cache behavior
Not installing React 19 before upgrading Next.js
use() and useActionStateKeeping custom webpack() config when using Turbopack
Not wrapping dynamic content in Suspense for Partial Prerendering
Using useMemo and useCallback with React Compiler enabled
Not testing production build after switching to Turbopack
Interview Questions on This Topic
What are the major changes in Next.js 16 compared to Next.js 15?
use() hook, enhanced Server Actions with useActionState, and React Compiler (experimental).
2. Turbopack is the default bundler — replaces Webpack for both development and production. Custom webpack configs must be migrated to the turbopack config object.
3. Fetch caching default (changed in v15, enforced in v16) — v15 switched fetch from force-cache to no-store. Next.js 16 adds 'use cache' and cacheLife APIs. Every fetch without explicit options hits origin unless configured.
4. Partial Prerendering is stable — combines static shell with dynamic streaming via Suspense boundaries.
5. New 'use cache' directive and cacheLife API — provide fine-grained component-level caching control.
The most disruptive change for v14 upgraders is the fetch caching default from v15.Frequently Asked Questions
That's React.js. Mark it forged?
7 min read · try the examples if you haven't