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 underreact-19-hooks/action-state
).
const submitActionWithCurrentState = async (
prevState: any,
formData: FormData
) => {
// do some action like adding the user into the users array
};
export default function ActionStateComponent() {
const [state, formAction] = useActionState(submitActionWithCurrentState, {
users: [],
error: null,
});
return (
<div>
<h1>useActionState Example</h1>
<form action={formAction} id="action-hook-form" className="mb-4">
<div>
<input
type="text"
name="username"
placeholder="Enter your name"
/>
<button
type="submit"
>
Submit
</button>
</div>
</form>
<div>{state?.error}</div>
{state?.users?.map((user: any) => (
<div key={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 underreact-19-hooks/use-form-status
).
const Form = () => {
const { pending, data } = useFormStatus();
return (
<div>
<input
type="text"
name="username"
placeholder="Enter your name"
/>
<button
disabled={pending}
type="submit"
>
Submit
</button>
{pending && (
<p>Submitting {data?.get('username') as string}...</p>
)}
</div>
);
};
-
useOptimistic
: Perfect for handling optimistic updates, allowing the UI to update instantly while a request is still in progress (see full example underreact-19-hooks/use-optimistic
).
const submitTitle = async (formData: FormData) => {
// Simulate server delay
await new Promise(resolve => setTimeout(resolve, 1000));
const newTitle = formData.get('title') as string;
if (newTitle === 'error') {
throw new Error('Title cannot be "error"');
}
return newTitle;
};
export default function OptimisticComponent() {
const [title, setTitle] = useState('Title');
const [optimisticTitle, setOptimisticTitle] = useOptimistic(title);
const [error, setError] = useState<string | null>(null);
const pending = title !== optimisticTitle;
const handleSubmit = async (formData: FormData) => {
setError(null);
setOptimisticTitle(formData.get('title') as string);
try {
const updatedTitle = await submitTitle(formData);
setTitle(updatedTitle);
} catch (e) {
setError((e as Error).message);
}
};
return (
<div>
<h1>useOptimistic Example</h1>
<h2>{optimisticTitle}</h2>
<p> {pending && 'Updating...'} </p>
<form action={handleSubmit}>
<input
type="text"
name="title"
placeholder="Change Title"
/>
<button
type="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 theno-store
strategy by default. To cache them from now onwards, we'll need to include explicit cache parameters.
For example:
async function getData() {
const res = await fetch('https://api.dimeloper.com/', { cache: 'force-cache' });
return res.json();
}
- 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.js
module.exports = {
experimental: {
staleTimes: {
dynamic: 30, // Manually set dynamic route staleTime to 30 seconds
static: 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
):
export default async function SearchPage({
searchParams,
}: {
searchParams: { query: string };
}) {
const params = await searchParams;
const query = params.query;
return (...)
}
Introducing the New <Form>
Component
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
):
export default function FormPage() {
return (
<div>
<h1>Next v15 Form Component</h1>
<p>Which saves us from a lot of boilerplate code.</p>
<Form action="/search">
{/* On submission, the input value will be appended to
the URL, e.g. /search?query=abc */}
<input
name="query"
placeholder="Enter your search query"
/>
<button
type="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.
Starter repository for v15 with examples
dimeloper / nextjs-v15-starter
A starter project that showcases v15 features.
Next.js v15 Features Examples
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.
Getting Started
First, install the dependencies:
yarn install
Then, run the development server (turbopack):
yarn dev
Open http://localhost:3000 with your browser to see the result.
Project Structure
The project is organized as follows:
├── app/
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.tsx
│ ├── form-showcase/
│ │ └── page.tsx
│ ├── search/
│ │ └── page.tsx
│ └── react-19-hooks/
│ ├── page.tsx
│ ├── action-state/
│ │ └── page.tsx
│ ├── use-optimistic/
│ │ └── page.tsx
│ ├── use-hook/
│ │ └── page.tsx
│ └── use-form-status/
│ └── page.tsx
├── public/
│ ├── next.svg
│ └── vercel.svg
├── .eslintrc.json
├── .prettierrc
├── next.config.js
├── package.json
├── postcss.config.js
├── README.md
…Wrapping Up
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.
Top comments (0)