What's New in Next.js 15: New Hooks, Turbopack and more
Dimitris Kiriakakis
Posted on October 23, 2024
Next.js has consistently pushed the boundaries of modern web development, and with version 15, they've introduced some nice features that improve both performance and the developer experience.
Let’s take a closer look at some of the most impactful updates with some example usages.
In the end of this article you will also get a Next.js v15 starter repository with detailed example usages, if you want to skip to the repo, just scroll to the bottom.
Full Support of React 19 with New Hooks
With Next.js 15, we now get full support for React 19, enabling its new features like the additional hooks. Let’s break down the most notable ones:
useActionState: This hook helps manage and display the state of ongoing actions in our UI (see full example under react-19-hooks/action-state).
constsubmitActionWithCurrentState=async (prevState:any,formData:FormData)=>{// do some action like adding the user into the users array};exportdefaultfunctionActionStateComponent(){const[state,formAction]=useActionState(submitActionWithCurrentState,{users:[],error:null,});return (<div><h1>useActionState Example</h1><formaction={formAction}id="action-hook-form"className="mb-4"><div><inputtype="text"name="username"placeholder="Enter your name"/><buttontype="submit">
Submit
</button></div></form><div>{state?.error}</div>{state?.users?.map((user:any)=>(<divkey={user.username}>
Name: {user.username} Age: {user.age}</div>))}</div>);}
useFormStatus: Ideal for tracking form submission states in real time. For example we can disable the button while our form is being submitted (see full example under react-19-hooks/use-form-status).
constForm=()=>{const{pending,data}=useFormStatus();return (<div><inputtype="text"name="username"placeholder="Enter your name"/><buttondisabled={pending}type="submit">
Submit
</button>{pending&&(<p>Submitting {data?.get('username')asstring}...</p>)}</div>);};
useOptimistic: Perfect for handling optimistic updates, allowing the UI to update instantly while a request is still in progress (see full example under react-19-hooks/use-optimistic).
constsubmitTitle=async (formData:FormData)=>{// Simulate server delayawaitnewPromise(resolve=>setTimeout(resolve,1000));constnewTitle=formData.get('title')asstring;if (newTitle==='error'){thrownewError('Title cannot be "error"');}returnnewTitle;};exportdefaultfunctionOptimisticComponent(){const[title,setTitle]=useState('Title');const[optimisticTitle,setOptimisticTitle]=useOptimistic(title);const[error,setError]=useState<string|null>(null);constpending=title!==optimisticTitle;consthandleSubmit=async (formData:FormData)=>{setError(null);setOptimisticTitle(formData.get('title')asstring);try{constupdatedTitle=awaitsubmitTitle(formData);setTitle(updatedTitle);}catch (e){setError((easError).message);}};return (<div><h1>useOptimistic Example</h1><h2>{optimisticTitle}</h2><p>{pending&&'Updating...'}</p><formaction={handleSubmit}><inputtype="text"name="title"placeholder="Change Title"/><buttontype="submit"disabled={pending}>
Submit
</button></form><div>{error&&error}</div></div>);}
Updated Caching Strategies
The Next.js team took developer feedback seriously when refining the caching system. Here's what changed:
No More Automatic Caching for fetch and Route Handlers: To provide more predictable behaviour, fetch requests and route handlers are no longer cached by default. fetch requests will use the no-store strategy by default. To cache them from now onwards, we'll need to include explicit cache parameters.
Client Router Caching Adjustments: Page components are no longer cached by default in the client router, which prevents potential issues when users expect real-time data updates. In v15 the client will always fetch the latest page component data during in-app navigation.
To retain the original caching behavior in Next.js 15, we can configure it manually as such:
// Next.js 15, next.config.jsmodule.exports={experimental:{staleTimes:{dynamic:30,// Manually set dynamic route staleTime to 30 secondsstatic:180},},};
Asynchronous Request-Specific APIs
A significant improvement is that request-specific APIs like headers, cookies, params, and searchParams are now asynchronous. For example in the search page that we want to render content depending on the searchParams we need to do the following (see full example within search/page.tsx):
Forms are a crucial part of any web app, and Next.js 15 introduces the <Form> component to streamline form functionality. Extending the traditional HTML <form> element, this component brings prefetching, client-side navigation, and progressive enhancement into the mix.
Here’s how it works (see full example within form-showcase/page.tsx):
exportdefaultfunctionFormPage(){return (<div><h1>Next v15 Form Component</h1><p>Which saves us from a lot of boilerplate code.</p><Formaction="/search">{/* On submission, the input value will be appended to
the URL, e.g. /search?query=abc */}<inputname="query"placeholder="Enter your search query"/><buttontype="submit">
Submit
</button></Form></div>);}
The <Form> component delivers:
Prefetching: It prefetches layout and loading UI when the form comes into view, speeding up navigation.
Client-side Navigation: On submission, shared layouts and client-side state are preserved.
Progressive Enhancement: Forms still work if JavaScript isn’t loaded, ensuring full-page navigation.
TurboPack: Turbocharged Development
Next.js 15 enables TurboPack during development. By running next dev --turbo, we can now experience blazing-fast local server startups and quicker code refreshes. On the Vercel app, for instance, TurboPack achieved a 75% faster local server startup and 95% faster code updates with Fast Refresh.
In my sample project we are defaulting the dev server to turbopack by adapting the development script as such:
"scripts":{"dev":"next dev --turbo",}
Static Route Indicator in Dev Mode
Another helpful addition in the development experience is the static route indicator. This feature helps us quickly identify which routes are static during the development process, providing useful insights into our application's structure.
ESLint 9 Support
Next.js 15 introduces support for ESLint 9, while remaining backwards-compatible with ESLint 8. Along with this, the update to eslint-plugin-react-hooks v5 ensures even better support for React hook usage, further improving code quality in our Next.js projects.
TypeScript in next.config.ts
For TypeScript enthusiasts (like myself), the next.config.js file now supports TypeScript, allowing developers to create next.config.ts files and benefit from TypeScript’s type checking.
Stable Instrumentation and the onRequestError Hook
Instrumentation is now stable in Next.js 15, thanks to the introduction of the register() API. This allows us to hook into the Next.js server lifecycle for performance monitoring, error tracking, and deep integration with observability libraries like OpenTelemetry.
There’s also a collaboration with Sentry, introducing the new onRequestError hook. This hook captures critical error context, helping us catch and track server-side issues.
This project demonstrates the usage of new React 19 hooks in a Next.js application, as well as new features introduced in Next.js 15. It includes examples of various hooks and components.
Next.js 15 brings us a lot of nice features that enhance both developer experience and performance. Make sure to try out these updates in your own projects to see the improvements for yourself.