Next.js 15 Production Checklist
A practical checklist to ship a rock-solid Next.js 15 app: performance, routing, caching, security, observability, and CI/CD.
August 13, 2025 (10d ago)
8 min read
Next.js 15 introduces significant changes that impact production deployments. With Turbopack approaching stability, new caching defaults, and enhanced App Router features, here's your comprehensive checklist for shipping production-ready applications in 2025.
App Router & Architecture
Complete Migration Strategy
// next.config.ts - New TypeScript support in Next.js 15 import { NextConfig } from 'next' const config: NextConfig = { experimental: { // Turbopack for dev (stable in 15) turbo: { // Configure Turbopack if needed }, // Cache Components (beta in 16, experimental in 15) cacheComponents: true, }, // TypeScript support for config files typescript: { // Type-safe configurations with autocompletion ignoreBuildErrors: false, }, } export default config
Migration Checklist:
- ✅ Fully migrate to App Router (avoid Pages Router mixing)
- ✅ Convert all
pages/api
routes toapp/api
Route Handlers - ✅ Use Server Components as default, minimize
"use client"
directives - ✅ Implement proper loading.tsx and error.tsx boundaries
- ✅ Leverage the new Static Route Indicator during development
Data Fetching & Caching
Next.js 15 changed caching defaults significantly. Understanding these changes is crucial for production performance.
// NEW: Explicit caching required in Next.js 15 async function getUser(id: string) { // No longer cached by default - must opt-in const response = await fetch(`/api/users/${id}`, { cache: 'force-cache', // Opt into caching next: { revalidate: 3600 } // Revalidate every hour }) return response.json() } // Route handlers are also uncached by default export async function GET() { const data = await fetchData() return Response.json(data, { headers: { 'Cache-Control': 's-maxage=3600, stale-while-revalidate=86400' } }) }
Caching Strategy Checklist:
- ✅ Audit all fetch calls - add explicit caching where needed
- ✅ Configure Route Handler caching manually
- ✅ Use
revalidatePath()
andrevalidateTag()
for targeted invalidation - ✅ Implement proper ISR with
revalidate
configuration - ✅ Monitor cache hit rates in production
Performance Optimization
Core Web Vitals Implementation
// components/PerformanceOptimized.tsx import { Image } from 'next/image' import { Suspense } from 'react' export function OptimizedLanding() { return ( <main> {/* LCP optimization with priority loading */} <Image src="/hero-image.jpg" alt="Hero image" width={1200} height={600} priority // Critical for LCP placeholder="blur" blurDataURL="data:image/jpeg;base64,..." /> {/* Lazy load below-fold content */} <Suspense fallback={<LoadingSkeleton />}> <BelowFoldContent /> </Suspense> </main> ) }
Turbopack Integration (2025 Status)
# Development (Stable in Next.js 15) npm run dev --turbo # Production build (Alpha in 15, Beta in 16) npm run build --turbo # Use cautiously in production
Turbopack Benefits:
- 76.7% faster local server startup for large apps
- 96.3% faster code updates with Fast Refresh
- 45.8% faster initial route compilation
- 30% reduction in memory usage during development
Performance Checklist:
- ✅ Enable Turbopack for development workflows
- ✅ Test
next build --turbopack
in staging (alpha quality) - ✅ Implement proper image optimization with
next/image
- ✅ Add preloading for critical routes and fonts
- ✅ Use dynamic imports for code splitting:
dynamic(() => import('./Component'))
- ✅ Monitor Core Web Vitals with Real User Monitoring (RUM)
Security Hardening
Comprehensive Security Headers
// middleware.ts - Production security headers import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const response = NextResponse.next() // Security headers for production response.headers.set('X-DNS-Prefetch-Control', 'on') response.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload') response.headers.set('X-Frame-Options', 'SAMEORIGIN') response.headers.set('X-Content-Type-Options', 'nosniff') response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin') response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()') // CSP for XSS protection response.headers.set( 'Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" ) return response }
Server Actions Security
// app/actions.ts - Secure server actions import { z } from 'zod' import { revalidatePath } from 'next/cache' const CreateUserSchema = z.object({ email: z.string().email(), name: z.string().min(1).max(100), }) export async function createUser(formData: FormData) { // Always validate input server-side const validatedFields = CreateUserSchema.safeParse({ email: formData.get('email'), name: formData.get('name'), }) if (!validatedFields.success) { return { error: 'Invalid input data' } } // Verify user authorization const session = await getSession() if (!session?.user) { return { error: 'Unauthorized' } } try { await createUserInDB(validatedFields.data) revalidatePath('/users') return { success: true } } catch (error) { return { error: 'Failed to create user' } } }
Security Checklist:
- ✅ Implement robust Content Security Policy (CSP)
- ✅ Enable all security headers via middleware or hosting platform
- ✅ Validate all Server Action inputs with libraries like Zod
- ✅ Use
server-only
package for sensitive server code - ✅ Keep dependencies updated with automated security scanning
- ✅ Sanitize any user-generated content (MDX/HTML)
- ✅ Implement proper authentication and authorization flows
Observability & Monitoring
Comprehensive Error Tracking
// lib/monitoring.ts - Production monitoring setup import { withSentry } from '@sentry/nextjs' // Error boundary for client components export function GlobalErrorBoundary({ children }: { children: React.ReactNode }) { return ( <ErrorBoundary fallback={<ErrorFallback />} onError={(error, errorInfo) => { console.error('Error caught by boundary:', error, errorInfo) // Send to monitoring service Sentry.captureException(error, { extra: errorInfo }) }} > {children} </ErrorBoundary> ) } // Server action error handling export async function monitoredServerAction(formData: FormData) { try { const result = await performAction(formData) return result } catch (error) { // Log server-side errors console.error('Server action failed:', error) Sentry.captureException(error) return { error: 'Action failed' } } }
OpenTelemetry Integration
// instrumentation.ts - Next.js 15 built-in instrumentation export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { const { NodeSDK } = await import('@opentelemetry/sdk-node') const { getNodeAutoInstrumentations } = await import('@opentelemetry/auto-instrumentations-node') const sdk = new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()], }) sdk.start() } }
Monitoring Checklist:
- ✅ Set up error reporting (Sentry, Bugsnag)
- ✅ Implement distributed tracing with OpenTelemetry
- ✅ Monitor Core Web Vitals in production
- ✅ Track API response times and error rates
- ✅ Set up alerts for performance degradation
- ✅ Monitor server action performance and errors
- ✅ Use Next.js Speed Insights for real-user data
SEO & Metadata Enhancement
Complete Metadata Setup
// app/layout.tsx - Enhanced metadata import type { Metadata } from 'next' export const metadata: Metadata = { title: { template: '%s | Your App', default: 'Your App - Description', }, description: 'Comprehensive description for SEO', keywords: ['keyword1', 'keyword2'], authors: [{ name: 'Your Name' }], creator: 'Your Company', publisher: 'Your Company', formatDetection: { email: false, address: false, telephone: false, }, openGraph: { type: 'website', locale: 'en_US', url: 'https://yourapp.com', siteName: 'Your App', images: [ { url: 'https://yourapp.com/og-image.jpg', width: 1200, height: 630, alt: 'Your App', }, ], }, twitter: { card: 'summary_large_image', title: 'Your App', description: 'Description for Twitter', images: ['https://yourapp.com/twitter-image.jpg'], }, robots: { index: true, follow: true, googleBot: { index: true, follow: true, 'max-video-preview': -1, 'max-image-preview': 'large', 'max-snippet': -1, }, }, }
Dynamic Sitemap Generation
// app/sitemap.ts - Dynamic sitemap import type { MetadataRoute } from 'next' export default async function sitemap(): Promise<MetadataRoute.Sitemap> { const baseUrl = 'https://yourapp.com' // Static pages const staticPages = [ '', '/about', '/contact', ].map(route => ({ url: `${baseUrl}${route}`, lastModified: new Date().toISOString(), changeFrequency: 'monthly' as const, priority: route === '' ? 1 : 0.8, })) // Dynamic pages from database const posts = await getPosts() const dynamicPages = posts.map(post => ({ url: `${baseUrl}/blog/${post.slug}`, lastModified: post.updatedAt, changeFrequency: 'weekly' as const, priority: 0.6, })) return [...staticPages, ...dynamicPages] }
CI/CD & Deployment Pipeline
Comprehensive GitHub Actions Workflow
# .github/workflows/production.yml name: Production Deploy on: push: branches: [main] jobs: test-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Type check run: npm run type-check - name: Lint run: npm run lint - name: Run tests run: npm run test - name: Build application run: npm run build env: NODE_ENV: production - name: Run E2E tests run: npm run test:e2e - name: Security audit run: npm audit --audit-level high - name: Deploy to production if: success() run: npm run deploy env: DEPLOYMENT_TOKEN: ${{ secrets.DEPLOYMENT_TOKEN }}
Environment Configuration
// lib/env.ts - Type-safe environment variables import { z } from 'zod' const envSchema = z.object({ NODE_ENV: z.enum(['development', 'test', 'production']), DATABASE_URL: z.string().url(), NEXTAUTH_SECRET: z.string().min(32), NEXTAUTH_URL: z.string().url(), NEXT_PUBLIC_APP_URL: z.string().url(), }) export const env = envSchema.parse(process.env)
CI/CD Checklist:
- ✅ Implement comprehensive testing pipeline (unit, integration, E2E)
- ✅ Add TypeScript strict checking in CI
- ✅ Use ESLint with Next.js recommended rules
- ✅ Run security audits on dependencies
- ✅ Implement visual regression testing
- ✅ Use preview deployments for feature branches
- ✅ Add performance budgets and monitoring
- ✅ Implement automated rollback on deployment failures
Production Deployment Considerations
Database & Infrastructure
- ✅ Ensure database and backend are in the same region as your app
- ✅ Implement proper database connection pooling
- ✅ Use caching layers (Redis) for session storage and frequently accessed data
- ✅ Configure CDN for static assets
Hosting Platform Optimization
- ✅ Configure proper Edge Functions for API routes requiring low latency
- ✅ Set up proper logging and log retention policies
- ✅ Implement health checks and uptime monitoring
- ✅ Configure auto-scaling based on traffic patterns
Performance Benchmarks for 2025
Recent metrics from production Next.js 15 applications show:
- 57.6% faster compile times with Turbopack (development)
- 30% reduction in memory usage during local development
- 20-40% improvement in Time to First Byte (TTFB) with proper Server Components usage
- 15-25% reduction in bundle sizes with enhanced tree-shaking
Quick Production Health Check
Use this final checklist before going live:
- ✅ Performance: Lighthouse score >90, Core Web Vitals in green
- ✅ Security: All security headers configured, dependencies updated
- ✅ SEO: Proper metadata, sitemap, robots.txt configured
- ✅ Monitoring: Error tracking, performance monitoring, alerts set up
- ✅ Caching: Explicit caching strategy implemented for Next.js 15
- ✅ CI/CD: Automated testing, type checking, security audits
- ✅ Backup: Database backups, deployment rollback strategy
Next.js 15 represents a significant evolution in the framework. The key to success is understanding the new caching defaults, leveraging Turbopack for development productivity, and implementing comprehensive monitoring. Start with this checklist and iterate based on your specific application needs and traffic patterns.
Here are some other articles you might find interesting.
Building Secure Authentication Layers: A Complete Guide for 2025
Comprehensive guide to implementing modern authentication systems with detailed comparisons, user flows, and security best practices for web applications.
Go Concurrency Patterns in Practice
Real-world patterns for building fast and reliable concurrent systems in Go using goroutines, channels, and contexts.
Subscribe to my newsletter
A periodic update about my life, recent blog posts, how-tos, and discoveries.
NO SPAM. I never send spam. You can unsubscribe at any time!