Next.js 15: The Features That Actually Matter
Next.js 15 changed a lot. Here's what affects your projects, what breaks, and when to upgrade.
Table of Contents
- The Breaking Changes
- 1. Async Request APIs
- 2. Caching Changes
- 3. Route Handlers Changed
- Features You'll Actually Use
- 1. Turbopack
- 2. Partial Prerendering
- 3. Server Actions Improvements
- 4. Improved Error Handling
- Performance Improvements
- Migration Guide
- Step 1: Check Dependencies
- Step 2: Update Next.js
- Step 3: Update TypeScript
- Step 4: Fix Async APIs
- Step 5: Fix Caching
- Step 6: Test Everything
- Common Issues After Upgrade
- Issue 1: "cookies is not a function"
- Issue 2: Data Not Updating
- Issue 3: Build Errors
- What We're Using
- Should You Learn It?
- Real Talk
Next.js 15: The Features That Actually Matter
Next.js 15 is here with React 19 support, async request APIs, and a bunch of changes that will break your build.
Here's what you need to know.
The Breaking Changes (Deal With These First)
1. Async Request APIs
This will break your code:
// Next.js 14 - Works
import { cookies, headers } from 'next/headers';
export default function Page() {
const cookieStore = cookies();
const headersList = headers();
return <div>{headersList.get('user-agent')}</div>;
}
// Next.js 15 - Required
import { cookies, headers } from 'next/headers';
export default async function Page() {
const cookieStore = await cookies();
const headersList = await headers();
return <div>{headersList.get('user-agent')}</div>;
}
Every component using cookies() or headers() needs to be async now.
Why: Better performance, proper async handling, fewer edge case bugs.
Real Impact: We had to update 23 components across 3 projects. Took about 2 hours per project.
2. Caching Changes
Next.js 14 cached everything by default. Next.js 15? Nothing cached by default.
// Next.js 14: Cached automatically
async function getData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
// Next.js 15: Not cached (fetches every time)
async function getData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
// Want caching? Be explicit:
async function getData() {
const res = await fetch('https://api.example.com/data', {
cache: 'force-cache' // or 'no-store'
});
return res.json();
}
Why This Matters: Your API calls might suddenly hit the server every time. Could slow down your app or hit rate limits.
Fix: Add explicit caching to fetch calls that should be cached.
3. Route Handlers Changed
// Next.js 14
export async function GET(request: Request) {
return Response.json({ data: 'hello' });
}
// Next.js 15 - Same, but context changed
export async function GET(
request: Request,
context: { params: Promise<{ id: string }> }
) {
const { id } = await context.params;
return Response.json({ id });
}
Params are now async. Update your route handlers.
Features You'll Actually Use
1. Turbopack (Finally Stable)
# Development with Turbopack
next dev --turbo
Speed improvements we measured:
- Cold start: 8s → 2s
- Hot reload: 1.2s → 0.3s
- Large project: 15s → 4s
That's 11 seconds saved every time you refresh. Adds up fast.
Catch: Some webpack plugins don't work with Turbopack. Check compatibility first.
2. Partial Prerendering (PPR)
Mix static and dynamic content on same page:
export const experimental_ppr = true;
export default async function ProductPage({ params }) {
// Static part (prerendered)
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* Dynamic part (rendered on request) */}
<Suspense fallback={<Skeleton />}>
<UserSpecificPrice productId={params.id} />
</Suspense>
</div>
);
}
Page loads instantly with static content. Dynamic parts stream in.
Real Use Case: E-commerce product pages. Product info is static, but price/availability changes per user.
3. Server Actions Improvements
Server Actions are more stable and faster:
'use server';
export async function createOrder(formData: FormData) {
const product = formData.get('product');
const quantity = formData.get('quantity');
// Validate
if (!product || !quantity) {
return { error: 'Missing fields' };
}
// Save to database
const order = await db.orders.create({
product,
quantity: parseInt(quantity),
});
revalidatePath('/orders');
return { success: true, orderId: order.id };
}
No API routes needed. Form submits directly to server function.
We use this for: Contact forms, order forms, booking systems. Cuts code in half.
4. Improved Error Handling
Better error messages in development:
// Before: Cryptic error
// Error: Cannot read property 'name' of undefined
// Now: Helpful error
// Error in /app/products/[id]/page.tsx
// Cannot read property 'name' of undefined
// at line 23: <h1>{product.name}</h1>
// Suggestion: Check if product exists before accessing properties
Saves debugging time. Especially helpful for junior devs.
Performance Improvements
We tested 3 production apps:
App 1 (E-commerce):
- Build time: 45s → 32s
- Page load: 1.2s → 0.8s
- Lighthouse score: 87 → 94
App 2 (Booking Platform):
- Build time: 38s → 25s
- Page load: 1.5s → 1.0s
- Lighthouse score: 82 → 91
App 3 (Admin Dashboard):
- Build time: 52s → 38s
- Page load: 1.8s → 1.2s
- Lighthouse score: 79 → 88
Consistent 20-30% improvement across the board.
Migration Guide
Step 1: Check Dependencies
npm outdated
Update packages that don't support Next.js 15 yet.
Step 2: Update Next.js
npm install next@15 react@19 react-dom@19
Step 3: Update TypeScript (if using)
npm install -D typescript@5.5 @types/react@19 @types/react-dom@19
Step 4: Fix Async APIs
Search for cookies() and headers():
# Find all usages
grep -r "cookies()" src/
grep -r "headers()" src/
Make those components async and await the calls.
Step 5: Fix Caching
Add explicit cache options to fetch calls:
// For data that changes rarely
fetch(url, { cache: 'force-cache' })
// For data that changes often
fetch(url, { cache: 'no-store' })
// For data that should revalidate
fetch(url, { next: { revalidate: 3600 } })
Step 6: Test Everything
npm run build
npm run start
Click through your entire app. Check console for errors.
Common Issues After Upgrade
Issue 1: "cookies is not a function"
// Wrong
const cookieStore = cookies();
// Right
const cookieStore = await cookies();
And make the component async.
Issue 2: Data Not Updating
Probably caching. Add cache: 'no-store' to fetch:
const res = await fetch(url, { cache: 'no-store' });
Issue 3: Build Errors
Check your middleware. Next.js 15 changed how middleware works with headers:
// middleware.ts
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Set headers on response, not request
response.headers.set('x-custom', 'value');
return response;
}
What We're Using
At Raspib, all new Next.js projects use version 15:
Using:
- Turbopack for dev (so much faster)
- Server Actions (less API code)
- Async request APIs (cleaner code)
- Explicit caching (better control)
Not Using Yet:
- PPR (still experimental)
- Some advanced features (waiting for stability)
Should You Learn It?
If you're learning Next.js: Start with 15. No point learning old patterns.
If you know Next.js 14: Upgrade side projects first. Get comfortable with changes. Then upgrade client work.
If you're building for clients: Wait 1-2 months. Let the ecosystem catch up. Then upgrade during maintenance window.
Real Talk
Next.js 15 is better than 14. Faster, cleaner, more explicit.
But the async changes will break your code. Budget time for migration.
Don't upgrade Friday afternoon. Do it Monday morning when you have time to fix issues.
Building with Next.js?
We build Next.js apps for Nigerian businesses. E-commerce, booking platforms, dashboards.
📞 WhatsApp: +234 708 711 0468
📧 info@raspibtech.com
📍 Lagos Island
Related:
Need Help with Your Project?
Let's discuss how Raspib Technology can help transform your business
Related Articles
Laravel 11: What Changed and Why You Should Care
Laravel 11 is out. Slimmer structure, better performance, and features that actually save time. Here's what matters.
Read more →Laravel 12: The Upgrade You've Been Waiting For
Laravel 12 brings major improvements. Here's what changed and why it matters for your projects.
Read more →PHP 8.3: Features That Make Your Code Better
PHP 8.3 added typed class constants, json_validate, and more. Here's what actually improves your code.
Read more →