What's New in Next.js 15: New Hooks, Turbopack and more

dimeloper

Dimitris Kiriakakis

Posted on October 23, 2024

What's New in Next.js 15: New Hooks, Turbopack and more

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).
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>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • 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).
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>
  );
};
Enter fullscreen mode Exit fullscreen mode
  • 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).
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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

For example:

async function getData() {
  const res = await fetch('https://api.dimeloper.com/', { cache: 'force-cache' });
  return res.json();
}
Enter fullscreen mode Exit fullscreen mode
  • 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
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

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 (...)
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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",
}
Enter fullscreen mode Exit fullscreen mode

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

GitHub logo 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
Enter fullscreen mode Exit fullscreen mode

Then, run the development server (turbopack):

yarn dev
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
dimeloper
Dimitris Kiriakakis

Posted on October 23, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related