HomeProjects
Experience

Home

Blog

Next.js

Upgrade nextjs 15v to nextjs 16

this blog is about upgrading your nextjs project from version 15 to 16. in this blog i am going to tell you all the required steps for the upgrade and the benefits that we will enjoy after the upgrading our project.

Ashish Bishnoi 19-11-2025

In this particular Blog I am gonna demonstrate the breaking changes that has occurred in Nextjs latest version 16 and guide you through how to migrate it from nextjs 15 and show you the complete guide on how to master nextjs 16 and its changes and performance improvements compare to previous version. I will be talking about Cache updates, turbopack update and lot more.


If you have an existing project in nextjs 15 then run the blow command in terminal for the migration.

npm install next@latest react@latest react-dom@latest


Turbopack by default

Starting with Next.js 16, Turbopack is stable and used by default with next dev and next build

Previously you had to enable Turbopack using --turbopack, or --turbo. package.json


{

"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"start": "next start"
}
}


and now in nextjs the build is much more fast, during production also because of turbopack.


Caching APIs


revalidateTag


revalidateTag has a new function signature. You can pass a cacheLife profile as the second argument.

app/actions.ts

'use server'

import { revalidateTag } from 'next/cache'

export async function updateArticle(articleId: string) {

// Mark article data as stale - article readers see stale data while it revalidates

revalidateTagarticle-${articleId}, 'max')

}

updateTag

updateTag is a new Server Actions -only API that provides read-your-writes semantics, where a user makes a change and the UI immediately shows the change, rather than stale data.

It does this by expiring and immediately refreshing data within the same request.

//app/actions.ts

'use server'

import { updateTag } from 'next/cache'

export async function updateUserProfile(userId: string, profile: Profile) {

await db.users.update(userId, profile)

// Expire cache and refresh immediately - user sees their changes right away

updateTaguser-${userId})

}

refresh

refresh allows you to refresh the client router from within a Server Action. app/actions.ts


'use server'

import { refresh } from 'next/cache'

export async function markNotificationAsRead(notificationId: string) {

// Update the notification in the database

await db.notifications.markAsRead(notificationId)

// Refresh the notification count displayed in the header

refresh()

}

Middleware to proxy

The middleware filename is deprecated, and has been renamed to proxy to clarify network boundary and routing focus.

The edge runtime is NOT supported in proxy. Rename your middleware file, middleware.ts to proxy.ts

A sample code for more understanding in nextjs cashing tags.

Earlier i was also confused in cashing and in nextjs16 the cashing problem is now sorted out , here is a simple code that make your cashing concept in nextjs 16 more clear.

this is a dynamic route in nextjs i have made it force-static to do the cashing.

// [slug]/page.tsx

import db from "@/lib/db";

import { unstable_cache } from "next/cache";

import { eq } from "drizzle-orm";

import Datatable from "./dataTable";

import { user } from "../schema";

// export const revalidate = 3600;

export const dynamic = "force-static";

export const revalidate = 86400;

export default async function Home({ params }: any) {

const slug = (await params).slug;

const userId = slug;

// for first time it will take the value from database and after that it will use the cashing value

const getCashedUser = unstable_cache(

async (userId) => await db.select().from(user).where(eq(user.name, userId)),

[userId],

{ tags: ["user-viewcount-" + userId, "max"] }

);

const data = await getCashedUser(userId);

return <Datatable data={data} />;

}



// [slug]/dataTable.tsx

// this file contains the client side onchange handler to make a call at backend and perform some action to database/change some value

"use client";

import { increaseValue } from "@/lib/helper";

import { useState } from "react";

const Datatable = ({ data }: any) => {

const [loading, setLoading] = useState(false);

async function handleincreaseclick(id: string) {

try {

setLoading(true);

await increaseValue(id);

} catch (error) {

console.log(error);

} finally {

setLoading(false);

}

}

return (

<div>

{data.map((item: any) => (

<div key={item.id}>

<p>{item.name}</p>

<p>{item.viewCount}</p>

<button onClick={() => handleincreaseclick(item.name)}>

{loading ? "Loading..." : "Increase"}

</button>

</div>

))}

</div>

);

};

export default Datatable;

// lib/helper.ts

in this server side function we revalidate the cashTag

"use server";

import { user } from "@/app/schema";

import db from "./db";

import { eq, sql } from "drizzle-orm";

import { refresh, revalidatePath, revalidateTag } from "next/cache";

export async function increaseValue(id: string) {

await db

.update(user)

.set({ viewCount: sq${user.viewCount} + 1 })

.where(eq(user.name, id));

// use the same tag for revalidate

revalidateTag("user-viewcount-" + id, "max");

// can also use revalidatePath to purge cashing of complete page

// revalidatePath("/");

// refresh function to refresh the value in the frontend side.

refresh();

}

Reefer my github reposetory for complete code.

https://github.com/ashish11011/nextjs-16-cache-practice

Contact Me