Next.js 15 - What's New?
- Published on
Next.js 15: A Comprehensive Overview
Table of Contents
Introduction
Next.js 15 brings significant improvements to development speed, caching behavior, and overall developer experience. Let's break down these exciting changes and see how they'll impact your development workflow. If you want to see the new changes in code, please have a look at my nextjs-15 repo where I have prepared those new changes for you: https://github.com/XamHans/nextjs-15-changes/tree/main/nextjs-15-changes
Breaking Changes
Async Request APIs: The New Standard
The most significant breaking change in Next.js 15 is the transition to asynchronous request APIs. This change affects how we handle:
- Cookies
- Headers
- Draft mode
- Parameters in various files
- Search parameters
Previously, you could access these directly:
// Old way
const cookies = cookies()
// New way
const cookies = await cookies()
This change enables the server to prepare content before requests arrive, improving overall performance. To help with migration, Next.js provides the @next/codemod
CLI tool for automatic code updates.
Revised Caching Behavior
Next.js 15 introduces two major changes to caching:
GET Route Handlers: No longer cached by default
- Previous versions cached automatically
- Now requires explicit opt-in
- Use
export const dynamic = 'force-static'
for caching
Client Router Cache: Page components aren't cached by default
- Default stale time is now zero
- Layouts maintain previous caching behavior
- JS files still cache for 5 minutes
To retain previous caching behavior, add this to your next.config.js
:
module.exports = {
experimental: {
clientRouterCacheConfig: {
default: {
revalidate: false
}
}
}
}
Developer Experience Improvements
Enhanced Error Handling
Hydration errors are now more developer-friendly:
- Detailed error messages
- Source code context
- Actionable fix suggestions
Turbopack Stability
Turbopack reaches stability with impressive improvements:
- 76.7% faster local server startup
- 96.3% faster code updates
- 45.8% faster initial route compilation
Enable it using:
next dev --turbo
Static Route Indicator
A new visual indicator during development shows whether routes are static or dynamic, helping developers optimize performance without diving into code.
Post-Response Code Execution
Next.js 15 introduces the ability to run code after sending responses to users:
import { unstable_after as after } from 'next/server'
export default function Page() {
after(() => {
// Analytics or logging code here
})
return <h1>Hello</h1>
}
Enable this feature in next.config.js
:
experimental: {
unstable_after: true
}
New Form Component
The new <Form>
component brings several improvements:
- Automatic layout prefetching
- State preservation during submissions
- Progressive enhancement support
- Works without JavaScript
import { Form } from 'next/form'
export default function Page() {
return (
<Form action="/api/submit">
{/* Form fields */}
</Form>
)
}
Instrumentation
Next.js 15 has stabilized the instrumentation feature, making it a robust solution for server monitoring and observability. The instrumentation.js
(or instrumentation.ts
) file now provides a stable API for integrating with the Next.js server lifecycle, offering powerful capabilities for monitoring, error tracking, and performance optimization.
Key Features
Server Lifecycle Integration
- Monitor server performance
- Track error sources
- Integrate with observability tools like OpenTelemetry
- Initialize services during server startup
Stable API
- No longer requires
experimental.instrumentationHook
configuration - Simplified setup process
- Reliable production usage
- No longer requires
Basic Implementation
Create an instrumentation.js
file in your project root:
export async function register() {
// Initialize observability tools
if (process.env.NEXT_RUNTIME === 'nodejs') {
const monitoring = await import('@your-monitoring-tool')
monitoring.init({
serviceName: 'your-app-name',
environment: process.env.NODE_ENV
})
}
}
Advanced Error Handling
Next.js 15 introduces a new onRequestError
hook, developed in collaboration with Sentry, that provides comprehensive error tracking capabilities:
export async function onRequestError(error, request, context) {
// Capture detailed error information
const errorContext = {
router: context.router, // 'pages' or 'app'
serverComponent: context.serverComponent,
serverAction: context.serverAction,
routeHandler: context.routeHandler,
middleware: context.middleware,
url: request.url,
method: request.method,
error: {
message: error.message,
stack: error.stack,
name: error.name
}
}
// Send to your observability service
await fetch('https://your-monitoring-service.com/api/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify(errorContext)
})
}
export async function register() {
// Initialize your observability SDK
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { initializeMonitoring } = await import('./monitoring')
await initializeMonitoring()
}
}
Error Context Information
The onRequestError
hook provides detailed context about errors, including:
- Router Type: Identifies whether the error occurred in the Pages Router or App Router
- Server Context: Pinpoints the exact server component where the error originated:
- Server Components
- Server Actions
- Route Handlers
- Middleware
- Request Details: Captures relevant request information for debugging
- Error Information: Full error stack traces and messages
Common Use Cases
- Performance Monitoring
export async function register() {
const { metrics } = await import('@monitoring/metrics')
metrics.trackServerStart()
metrics.watchMemoryUsage()
metrics.watchCPUUsage()
}
- Database Connection Monitoring
export async function register() {
const { db, monitor } = await import('./database')
db.on('connect', () => monitor.logConnection())
db.on('error', (error) => {
monitor.logError(error)
// Implement retry logic or alerting
})
}
- OpenTelemetry Integration
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { NodeSDK } = await import('@opentelemetry/sdk-node')
const { trace } = await import('@opentelemetry/api')
const sdk = new NodeSDK({
serviceName: 'your-next-app',
traceExporter: yourPreferredExporter()
})
sdk.start()
}
}
This stable instrumentation API provides a powerful foundation for monitoring and debugging Next.js applications in production. By leveraging these features, developers can build more reliable and observable applications while maintaining high performance and user experience.
Security and Deployment
Enhanced Server Actions Security
Two major security improvements:
Dead Code Elimination
- Unused server actions are removed
- Reduces bundle size
- Improves security
Secure Action IDs
- Non-deterministic ID generation
- IDs change between builds
- Prevents endpoint guessing
Self-Hosting Improvements
Next.js 15 makes self-hosting easier:
- Custom Cache-Control headers are respected
- Automatic sharp installation with
next start
- Better CDN compatibility
- Default expiry time of one year
Migration Considerations
When upgrading to Next.js 15:
- Update async request APIs
- Review caching strategy
- Test server actions
- Check custom cache headers
Conclusion
Next.js 15 brings significant improvements to performance, security, and developer experience. While the async API changes require some migration effort, the benefits in performance and security make the upgrade worthwhile.
For more detailed information, visit the official Next.js documentation.
If you found this content helpful ⇢