nextjs

5 amazing new features in the new Next.js 15

Next.js 15 is here and things are better than ever!

From a brand new compiler to 700x faster build times, it’s never been easier to create full-stack web apps with exceptional performance.

Let’s explore the latest features from v15:

1. create-next-app upgrades: cleaner UI, 700x faster build

Reformed design

From this:

To this:

Webpack Turbopack

Turbopack: The fastest module bundler in the world (or so they say):

  • 700x faster than Webpack
  • 10x faster than Vite

And now with v15, adding it to your Next.js project is easier than ever before:

2. React Compiler, React 19 support, and user-friendly errors

React Compiler is a React compiler (who would’ve thought).

A modern compiler that understands your React code at a deep level.

Bringing optimizations like automatic memoization — destroying the need for useMemo and useCallback in the vast majority of cases.

Saving time, preventing errors, speeding things up.

And it’s really easy to set up: You just install babel-plugin-react-compiler:

JavaScript
npm install babel-plugin-react-compiler

And add this to next.config.js

JavaScript
const nextConfig = { experimental: { reactCompiler: true, }, }; module.exports = nextConfig;

React 19 support

Bringing upgrades like client and server Actions.

Better hydration errors

Dev quality of life means a lot and error message usefulness plays a big part in that.

Next.js 15 sets the bar higher: now making intelligent suggestions on possible ways to fix the error.

Before v15:

Now:

You know I’ve had a tough time in the past from these hydration errors, so this will certainly be an invaluable one for me.

3. New caching behavior

No more automatic caching!

For all:

  • fetch() requests
  • Route handlers: GET, POST, etc.
  • <Link> client-side navigation.

But if you still want to cache fetch():

JavaScript
// `cache` was `no-store` by default before v15 fetch('https://example.com', { cache: 'force-cache' });

Then you can cache the others with some next.config.js options.

4. Partial Prerendering (PPR)

PPR combines static and dynamic rendering in the same page.

Drastically improving performance by loading static HTML instantly and streaming the dynamic parts in the same HTTP request.

JavaScript
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> </> ); }

All you need is this in next.config.js:

JavaScript
const nextConfig = { experimental: { ppr: 'incremental', }, }; module.exports = nextConfig;

5. next/after

Next.js 15 gives you a clean way to separate essential from non-essential tasks from every server request:

  • Essential: Auth checks, DB updates, etc.
  • Non-essential: Logging, analytics, etc.
JavaScript
import { unstable_after as after } from 'next/server'; import { log } from '@/app/utils'; export default function Layout({ children }) { // Secondary task after(() => { log(); }); // Primary tasks // fetch() from DB return <>{children}</>; }

Start using it now with experimental.after:

JavaScript
const nextConfig = { experimental: { after: true, }, }; module.exports = nextConfig;

These are just 5 of all the impactful new features from Next.js 15.

Get it now with npx create-next-app@rc and start enjoying radically improved build times and superior developer quality of life.

How to allow all domains for images in Next.js

To allow Next.js to display images from all domains, modify your next.config.js file to add an object with protocol and hostname properties to the remotePatterns array property.

next.config.js
/** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, // other properties... remotePatterns: [ { protocol: 'https', hostname: '**', }, ], }; module.exports = nextConfig;

Why would I need to allow all domains for Next.js images?

Because you want to avoid this error:

The "hostname is not configured under images in your next.config.js" error occuring in a Next.js application.
Using a non-matching domain results in this error.

Which happens when you have next.config.js with a limited number of specified domains, for example:

Only images from upload.wikimedia.org are allowed in this Next.js app.
Only images from upload.wikimedia.org are allowed in this Next.js app.

But you try to display images from a domain not in the image.domains list:

src/app/blog/page.tsx
import React from 'react'; import { Metadata } from 'next'; import Image from 'next/image'; export const metadata: Metadata = { title: 'Coding Beauty', description: 'Tutorials by Coding Beauty', }; export default function Page() { return ( <main> <Image src="https://wp.codingbeautydev.com/wp-content/uploads/2023/08/vscode-tips-tricks.png" alt="Coding Beauty article featured image" width={100} height={100} ></Image> 10 essential VS Code tips & tricks for greater productivity </main> ); }

remotePatterns vs images.domains property in next.config.js

In this case, you can follow the steps earlier in this article:

next.config.js
/** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, // other properties... remotePatterns: [ { protocol: 'https', hostname: '**', }, ], }; module.exports = nextConfig;

Or you can modify your next.config.js to include wp.codingbeautydev.com:

Images can now also be displayed from wp.codingbeautydev.com in this Next.js app.
Images can now also be displayed from wp.codingbeautydev.com in this Next.js app.

Of course, if you don’t have any domains property, you just create one with the new domain as its single element.

While the image.domains is simple and straightforward, the remotePatterns property gives us more flexibility with its support for wildcard pattern matching, and protocol, port, and pathname restriction.

These advanced restrictions remotePatterns provide greater security when you don’t own all the content in the domain, which is why the Next.js team recommends it.

Why do we need to be explicit with domains for Next.js images?

So whether you use remotePatterns or images.domains, you still need to be explicit about the domains that a next/image component can display.

By doing so, you can prevent the potential security risks of loading images from random, unverified URLs that could contain malicious code.

How to quickly listen for a route/page change in Next.js

To detect a route change in Next.js:

  • app directory: use useEffect and usePathname.
  • pages directory: use useEffect and useRouter.

Listen for route/page change in Next.js app directory

To listen for a route change in Next.js 13 app directory, use the useEffect and the usePathname hooks. The action in useEffect will run anytime the pathname from usePathname changes.

src/app/route-change-listener.tsx
'use client'; import { usePathname } from 'next/navigation'; import { useEffect, useState } from 'react'; export function RouteChangeListener() { const pathname = usePathname(); const [changes, setChanges] = useState(0); useEffect(() => { console.log(`Route changed to: ${pathname}`); setChanges((prev) => prev + 1); }, [pathname]); return <></>; }

Here we log to the console and change the state of the component when the route changes.

The URL change listener logs to the console when the route changes.
The URL change listener logs to the console when the route changes.

Page change listener must persist with client-side routing

For this detection to work, the component containing this useEffect needs to be somewhere in the DOM where it will not get unmounted with client-side navigation.

In the Next.js 13 app directory, this could be the layout.tsx file:

src/app/layout.tsx
import { Metadata } from 'next'; import '@/styles/globals.css'; import { RouteChangeListener } from './route-change-listener'; export const metadata: Metadata = { title: 'Coding Beauty', description: 'The official Coding Beauty home page.', icons: { icon: '/favicon.png', }, }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> {/* 👇 Persists with client-side navigation */} <RouteChangeListener /> <body>{children}</body> </html> ); }

useEffect needs 'use client'

Also, since server components are the default in Next.js 13, you’ll need to add the 'use client' directive at the top of the component file.

Otherwise, you won’t be able to use interactive client-side features like React hooks, including useEffect.

The useEffect React hook can't work without the 'use client' directive in Next.js.
useEffect can’t work without ‘use client’ in Next.js.

Listen for route/page change in Next.js pages directory

To handle a URL or location change in Next.js pages directory, combine the useEffect and the useRouter hooks:

src/pages/url-change-listener.tsx
import { useEffect } from 'react'; import { useRouter } from 'next/router'; export function UrlChangeListener() { const router = useRouter(); useEffect(() => { console.log(`The page is now: ${router.pathname}`); }, [router]); return <></>; }

Route change detector must persist with client-side routing

Just like with the app directory, the component that listens for the URL change with useEffect needs to be somewhere in the DOM where it will not get unmounted with client-side navigation.

In the pages directory, this could be the _app.tsx or _app.js file:

src/pages/_app.tsx
import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { UrlChangeListener } from './url-change-listener'; export default function App({ Component, pageProps }: AppProps) { return ( <> <UrlChangeListener /> <Component {...pageProps} /> </> ); }

Detect route/page with useRouter() events

Alternatively, we can detect a client-side URL change in the Next.js pages directory with events that the useRouter() object emits. For example:

src/pages/url-change-listener.tsx
import { useEffect } from 'react'; import { useRouter } from 'next/router'; export function UrlChangeListener() { const router = useRouter(); useEffect(() => { const startHandler = () => { console.log('Router change started'); }; const completeHandler = () => { console.log('Router change completed'); }; router.events.on('routeChangeStart', startHandler); router.events.on('routeChangeComplete', completeHandler); return () => { router.events.off('routeChangeStart', startHandler); router.events.off('routeChangeComplete', completeHandler); }; }, []); // 👇 You can put a progress bar or something here return <></>; }

Here we used two important router events:

  • routeChangeStart – fires when the route is about to change.
  • routerChangeComplete – fires when the router has changed completely.

There’s more too, and their names are just as self-explanatory as these two.

The `useRouter()` object has other properties apart from routeChangeStart and routeChangeComplete.

Key takeaways

  • To listen for a route or page change in Next.js app directory, combine the useEffect and usePathname hooks.
  • Detect a URL change in the pages directory with the useEffect and useRouter hooks.
  • You can also use the routeChangeStart and routeChangeComplete events from the useRouter() object to handle a location change.