I’ve been working with Next.Js for a quite a while and have been watching its development with interest all this time. Last week I was happy attended Next.js Conf in San Francisco. Perficient was proud to sponsor the event, allowing us to showcase our Sitecore Practice alongside my colleague David Lewis from the Optimizely Practice.
Vercel released new version 15 of Next.js framework. That was truly innovative day and I’d like to share my takeaways from the event about this new version.
Vercel seriously worked on mistakes
In the past, the Next.js team made some questionable decisions, such as rushing releases and maintaining rigid opinions despite community feedback. This included changes like rewriting the fetch API, implementing hard caching, and introducing numerous bugs, among other issues, while once again overlooking community requests. It took nearly a year for the team to recognize that these approaches were ineffective and to begin addressing the underlying problems. With the release of version 15, there is finally a sense that the framework is truly meeting the needs of its community, much as it has successfully done in previous iterations.
React 19
We are currently facing an unusual situation. Over six months have passed since the release candidate of React.js was introduced, yet the stable version has not been published. This delay directly impacts Next.js, as the two frameworks are closely intertwined. As a result, Next.js is currently utilizing the release candidate version of React.js, but this is only partially accurate. In reality, Next.js employs two different React.js configurations:
- React.js 19 Canary for the App Router
- React.js 18 for the Pages Router
Interestingly, there was an initial plan to integrate the React.js 19 version for the Pages Router as well. However, these changes were later rolled back. Full support for React.js version 19 is expected once the stable release is officially launched.
Form component
This next.js innovation is in fact already familiar form from react-dom
, but with some improvements. You benefit from Next.Js implementation primarily in cases when a successful form submission involves a transition to another page. In that case, the loading.tsx
and layout.tsx
abstractions for the following page will get preloaded.
import Form from 'next/form' export default function Page() { return ( <Form action="/search"> {/* On submission, the input value will be appended to the URL, e.g. /search?query=abc */} <input name="query" /> <button type="submit">Submit</button> </Form> ) }
Developer Experience (DX)
When discussing Next.js, the developer experience (DX) is impossible to overlook. Beyond the typical “Faster, Higher, Stronger” claims, Next.js has introduced several meaningful improvements that significantly enhance DX:
- Long-awaited support for ESlint v9. Next.js never supported ESlint v9. This is despite the fact that both eslint (v8) and some of its own dependencies were already marked as deprecated. Because of that developers were essentially forced to keep deprecated packages.
- The error interface in next.js – which is already clear and convenient – was slightly improved:
- Added a button to copy the call stack;
- Added the ability to open the source of the error in the editor on a specific line.
- Added Static Indicator – an element in the corner of the page showing that the page is built in static mode. The pre-built page indicator has been with us for years so it was slightly updated and adapted for App Router.
- Also added a directory with debug information – .next/diagnostics. That’s where one can find information about the build process and all errors that occur (sometimes helps to parse problems).
Versioning the documentation
One particularly valuable enhancement is the ability to access different versions of the documentation. But why is this so crucial for the developer experience?
Updating Next.js to accommodate major changes can be a challenging and time-consuming task. As a result, older versions like Next.js 12 and 13 remain widely used, with over 2 million and 4 million monthly downloads respectively. Developers working with these versions need documentation that is specific to their setup, as the latest documentation may include significant changes that are not compatible with their projects. By providing versioned documentation, Next.js ensures that developers have the reliable resources they need to maintain and update their applications
Turbopack
Probably the biggest news:
- Turbopack is now fully complete for development mode! “100% of existing tests ran with no errors for Turbopack”
- Now the turbo team is working on the production version, progressively going through the tests and covering them all (currently about 96%)
Turbopack introduces a range of new features that enhance its functionality and performance:
- Setting a memory limit for a Turbopack build;
- Tree Shaking (in other words that is removal of the unused code):
-
const nextConfig = { experimental: { turbo: { treeShaking: true, memoryLimit: 1024 * 1024 * 512 // bytes (512MB) }, }, }
These turbopack change alone reduced memory usage by 25-30% and speeded up heavy page assembly by 30-50%.”
- Fixed significant issues with styles. In version 14, there were often situations when the styles were broken in order during navigation, and because of this, style A became higher than style B, then lower. This changed their priority and, accordingly, the elements looked different.
- The next long-awaited improvement. Now you can write the configuration in TypeScript, and the file correspondingly would be next.config.ts:
import type { NextConfig } from 'next'; const nextConfig: NextConfig = { /* here goes you config */ }; export default nextConfig;
Same strongly-typed syntax as usual, but very nice to have, finally!
- Another interesting innovation is retrying a failed page generation before actually failing the build for static pages. If the page fails the assembly for the connectivity issues, it will try it again:
const nextConfig = { experimental: { staticGenerationRetryCount: 3, }, }
Framework API changes
Updating Next.js often involves some of the most challenging aspects, and version 15 is no exception with its critical enhancements.
One significant change in version 15 is the transition of several framework APIs to asynchronous operations. This shift particularly affects the core framework-centric abstractions, including:
cookies
,headers
,params
and- searchParams (also called Dynamic APIs).
import { cookies } from 'next/headers'; export async function AdminPanel() { const cookieStore = await cookies(); const token = cookieStore.get('token'); // ... }
The changes are big indeed, but the Next.js team suggests one could update to the new APIs automatically by calling their codemod
:
npx @next/codemod@canary next-async-request-api .
Caching
In my opinion, that is where the most important changes have happened. And the most important news is that Caching is now disabled by default!
Let’s take a look on what’s changed:
- Actually, fetch now uses the no-store value by default instead of force-cache;
- API routes use force-dynamic mode by default (previously it was force-static by default);
- Caching in the client router has also been disabled. Previously, if a client visited a page within the path, it was cached on the client and remained in this state until the page reload. Now the current page will be loaded each time. This functionality can be altered via next.config.js:
const nextConfig = { experimental: { staleTimes: { dynamic: 30 // defaults to 0 }, }, }
- Moreover, even if client caching is enabled, it most likely will be updated at the correct time. Namely, if the enabled page cache on the server expires.
- Server components are now cached in development mode. Due to this, updates in development are faster.
- Following the above, one can reset the cache by just reloading a page or can also completely disable the functionality via next.config.js:
const nextConfig = { experimental: { serverComponentsHmrCache: false, // defaults to true }, }
- You can control the “Cache-Control” header which was previously always overwritten with the internal values of next.js. This caused artifacts with caching via CDN;
- next/dynamic caches modules for reuse, rather than loading again each time;
Partial Prerendering (PPR)
This could be the main teaser of the release. PPR is a page assembly mode, in which Next.Js prerenders and caches as much of the route as possible,. while some individual elements are built on each request. In this case, the pre-assembled part is immediately sent to the client, and the remaining are loaded dynamically.
The feature existed already six months ago in the release candidate as an experimental API. Previously PPR was enabled for the entire project, but since now it one can enable it for each segment (layout or page):
export const experimental_ppr = true
Another change is Partial Fallback Prerendering (PFPR). Due to this improvement, the pre-assembled part is immediately sent to the client, and the rest are loaded dynamically. At this time, a callback component is shown in place of the dynamic elements.
import { Suspense } from "react" import { StaticComponent, DynamicComponent } from "@/app/ui" export const experimental_ppr = true export default function Page() { return { <> <StaticComponent /> <Suspense fallback={...}> <DynamicComponent /> </Suspense> </> }; }
Instrumentation
Instrumentation comes as a stable API. The instrumentation file allows users to affect Next.js server lifecycle. Works universally with all Pages Router and App Router segments.
Currently, instrumentation supports hooks:
- register – called once when initializing the next.js server. Can be used for integration with monitoring libraries (OpenTelemetry, datalog) or for specific project tasks.
- onRequestError – a new hook called on all server errors. Can be used for integration with error tracking libraries (Sentry).
Interceptor
Interceptor is route-level middleware. It feels as something like a full-fledged existing middleware, but, unlike the one:
- Can work in node.js runtime;
- Works on the server, therefore has access to the environment and a single cache;
- Can be added multiple times and is nesting inherited (like middleware worked when it was in beta);
- Works, among other things, for server functions.
In this case, when creating an interceptor file, all pages underneath the tree become dynamic.
- If we keep Vercel in mind, now middleware will be effective as a primary simple check at the CDN level (so that it could immediately return redirects if the request is not allowed), and interceptors will work on the server, doing full checks and complex operations.
- For the self-host, apparently, such a division will be less effective since both abstractions work on the server. Perhaps it will be enough just to use only interceptor.
Welcome v0 – Vercel’s new Generative UI
Last but not least, Next.js introduces Generative UI (v0), a groundbreaking feature that combines the best practices of frontend development with the full potential of generative AI. Currently in Beta, I had the opportunity to experience Generative UI firsthand at the event. I was thrilled to see how powerful and intuitive it is—from the very first prompt, it successfully generated the configuration for Sitecore!
I am thrilled to conclude that our toolbelt has been enriched with new, practical tools that enable us to deliver exceptional solutions effortlessly, eliminating the need to reinvent the wheel.
Well done, Vercel! Thanks everyone building this wonderful ecosystem: