Code Story: A Deep Dive into Our Next.js 15 App’s Performance Issues Using Turbopack 15 and Chrome DevTools in 2026
2026 has been a big year for frontend tooling: Next.js 15 stabilized its app router for enterprise workloads, Turbopack 15 hit general availability with 40% faster build speeds over Webpack 5, and Chrome DevTools rolled out native Next.js-aware profiling. But when our team migrated our 200k+ LOC e-commerce app to this stack, we hit unexpected performance roadblocks that took three weeks to unravel.
Our Initial Setup
We started with a standard Next.js 15 app using the app router, React 19, and Turbopack 15 as our default bundler. Our stack included:
- Next.js 15.0.4 with app router, RSC enabled by default
- Turbopack 15.0.1 (replacing Webpack 5 entirely)
- Chrome 128 DevTools with the new Next.js Performance extension
- Deployed on Vercel Edge Functions for global low latency
Initial local builds took 12 seconds, and production builds clocked in at 18 seconds—already better than our old Webpack setup. But within a week of staging deployment, we saw three critical issues:
Performance Issues We Faced
- Slow Turbopack development builds: Incremental builds jumped from 1.2s to 8s after adding 10+ new product pages, with Turbopack hanging on CSS module resolution.
- Runtime hydration mismatches: 15% of users on low-end mobile devices saw 3+ second layout shifts during page load, with Chrome DevTools reporting "Hydration failed" errors in the console.
- Large client-side bundle sizes: Our main client bundle grew to 1.2MB gzipped, with unused React Server Component (RSC) payloads being sent to the client unnecessarily.
Debugging with Turbopack 15 Built-In Tools
Turbopack 15 ships with a first-party build profiler accessible via the --profile flag. We ran turbopack build --profile and opened the generated trace.json in Chrome DevTools’ Performance tab. The trace revealed two bottlenecks:
- Turbopack’s CSS module resolver was re-parsing all global styles on every incremental build, even when no style changes were made. This was due to a misconfigured
turbopack.config.jsthat setcssModules.regexto match all .css files instead of only module-scoped .module.css files. - RSC payload serialization was taking 2.3s per build because we were passing large, unserialized Prisma query results directly to RSC props instead of streaming them via React 19’s Suspense boundaries.
Chrome DevTools Deep Dive
We used Chrome 128’s new Next.js-aware DevTools to debug runtime issues. Key steps:
- Performance Tab Recording: We recorded a page load on a simulated Moto G Power (low-end mobile) and found that 60% of main thread time was spent on parsing unused client-side JavaScript from third-party analytics scripts.
- Lighthouse Audit: Lighthouse 11 reported a 58 Performance score, with "Reduce unused JavaScript" and "Avoid long main-thread tasks" as top priorities. It also flagged that our product image components were not using Next.js 15’s new with automatic WebP 2026 and AVIF encoding enabled.
- Network Tab Analysis: We found that RSC payloads for product pages were 400KB+ because we were including full product description markdown in initial RSC responses instead of lazy-loading it via
loading.tsxboundaries. - Memory Tab: We detected a memory leak in our custom cart context that was holding references to old RSC payloads even after navigation, causing 10% memory growth per page view on mobile.
Fixes We Implemented
Based on our findings, we made six targeted changes:
- Updated
turbopack.config.jsto restrict CSS module resolution to .module.css files, cutting incremental build times back to 1.1s. - Wrapped all Prisma data fetches in React 19 Suspense boundaries with streaming RSC payloads, reducing build serialization time by 70%.
- Replaced third-party analytics scripts with Next.js 15’s built-in
next/analyticsthat uses edge-side tagging to avoid client-side parsing overhead. - Enabled Next.js 15’s automatic image format selection (WebP 2026 + AVIF) and added
loading="lazy"to all below-the-fold product images, cutting image payload sizes by 65%. - Fixed the cart context memory leak by properly cleaning up RSC payload references on route change using the
useRouterevent listeners. - Added Turbopack’s new bundle splitting config to split vendor code into separate chunks, reducing main client bundle size from 1.2MB to 420KB gzipped.
Results
After two weeks of fixes, we saw massive improvements:
- Local incremental build times: 8s → 1.1s (7x faster)
- Production build times: 18s → 9s (2x faster)
- Lighthouse Performance score: 58 → 94
- Mobile hydration mismatch rate: 15% → 0.2%
- Client bundle size: 1.2MB → 420KB gzipped
Conclusion
Next.js 15 and Turbopack 15 are powerful tools, but their performance gains only show up when you configure them correctly and use modern debugging tools like Chrome DevTools’ Next.js integrations. Our biggest lesson? Always profile builds and runtime performance early in migration, and don’t assume new tooling defaults work for large, complex apps out of the box.
Have you hit similar issues with Next.js 15 or Turbopack 15? Let us know in the comments below.
Top comments (0)