Trang chủ Kiến Thức Công Nghệ NextJs 15 có gì mới? So sánh NextJs 14 và NextJs 15
Công Nghệ

NextJs 15 có gì mới? So sánh NextJs 14 và NextJs 15

Chia sẻ
NextJs 15 có gì mới? So sánh NextJs 14 và NextJs 15
Chia sẻ

Đến hẹn lại lên, cứ sau khoảng 365 ngày thì NextJs lại có bản update version lớn một lần. Chúng ta lại có dịp so sánh phiên bản NextJs 15 và NextJs 14 với nhau.

Đương nhiên rồi, so sánh thì phải có bằng chứng, bằng chứng ở đâu thì cùng mình create-next-app@14create-next-app@latest và quan sát 2 file package.json của 2 phiên bản sau nha:

So sánh next 14 và next 15

Bạn có thể thấy phần scripts, câu lệnh dev bên version 15 có thêm –turbopack

  • next dev: khởi chạy server của Next.js. Đây là lệnh để build ứng dụng trong môi trường dev, nhưng với các ứng dụng lớn, thời gian khởi động có thể lâu hơn.
  • next dev –turbopack: đây là version ổn định mới của Next 15, tối ưu tốc độ bằng cách cải tiến hiệu suất với Turbopack. Khởi động server và cập nhật code nhanh hơn đáng kể so với next dev thông thường.

Tiếp theo, phần dependencies bạn có thể thấy được bản next 15 sử dụng react và react-dom version 19rc (19rc là version 19 thử nghiệm) còn với bản next 14 sử dụng react và react-dom version 18 chính thức.

Nhưng bạn có thể thấy phần type thì bên team Next vẫn giữ version 18 để đảm bảo hơn trong quá trình dev. Đấy là tổng quan về file package.json của next 14 và next 15, tổng quan rồi thì cùng mình vào chi tiết nha.

1. React 19

  • useFormState đã được thay thế bởi useActionState. useFormState bạn vẫn có thể sử dụng được nhưng sẽ bị loại bỏ trong tương lai.
  • useFormStatus có thêm các key như: data, method, và action, bình thường bạn sử dụng thì chỉ có key pending mà thôi.

2. Async Request APIs

Trong Next.js 15, các API mình nói bên dưới đã chuyển từ đồng bộ sang bất đồng bộ, giúp tối ưu hiệu suất và khả năng xử lý:

  • cookies
  • headers
  • draftMode
  • params trong layout.js, page.js, route.js, default.js,…
  • searchParams trong page.js

Về mặt hỗ trợ chuyển đổi, Next.js cung cấp công cụ codemod để tự động cập nhật code. Các API này vẫn có thể truy cập tạm thời theo cách đồng bộ, nhưng sẽ warning.

Theo chân mình đi xem các API này thay đổi cụ thể như thế nào nhé!

2.1 Cookies

TypeScript

import { cookies } from 'next/headers'
 
// Trước: Đồng bộ
const cookieStore = cookies()
const token = cookieStore.get('token')
 
// Sau: Bất đồng bộ
const cookieStore = await cookies()
const token = cookieStore.get('token')

Bên trên là code minh họa cách chuyển từ việc truy cập đồng bộ API cookies sang bất đồng bộ trong Next 15.

Bạn vẫn có thể call API cookies cách đồng bộ (không cần await), nhưng nó chỉ là giải pháp tạm thời để đỡ khó khăn trong quá trình chuyển đổi. Tuy nhiên, khi sử dụng cách này, bạn sẽ thấy warning trong môi trường dev.

Typescript

import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
 
// Trước
const cookieStore = cookies()
const token = cookieStore.get('token')
 
// Sau
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// Sẽ log warning trong môi trường dev
const token = cookieStore.get('token')

Lý do warning, là vì Next khuyến nghị bạn nên chuyển sang gọi bất đồng bộ await cookies() để đảm bảo sự tương thích tốt hơn.

2.2 Headers

Đoạn code bên dưới là cách thay đổi từ việc gọi đồng bộ sang bất đồng bộ với API headers trong Next.js 15.

Typescript

import { headers } from 'next/headers'
 
// Trước: Đồng bộ
const headersList = headers()
const userAgent = headersList.get('user-agent')
 
// Sau: Bất đồng bộ
const headersList = await headers()
const userAgent = headersList.get('user-agent')

Tương tự như với cookies thì bạn vẫn có thể call API headers cách đồng bộ (không cần await). Vẫn sẽ warning trong môi trường dev nhé.

Typescript

import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
 
// Trước
const headersList = headers()
const userAgent = headersList.get('user-agent')
 
// Sau: sẽ warning trong môi trường dev
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
const userAgent = headersList.get('user-agent')

2.3 draftMode

Dưới đây là đoạn code thay đổi từ việc call API đồng bộ sang bất đồng bộ với draftMode

Typescript

import { draftMode } from 'next/headers'
 
// Trước: đồng bộ
const { isEnabled } = draftMode()
 
// Sau: bất đồng bộ
const { isEnabled } = await draftMode()

Nếu bạn vẫn muốn sử dụng đồng bộ theo cách cũ, thì vẫn có thể nhé. Nhưng sẽ warning trong môi trường dev

Typescript

import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
 
// Trước
const { isEnabled } = draftMode()
 
// Sau: sẽ log warning in dev
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode

2.4 params & searchParams

  • Asynchronous Layout

Các tham số params và searchParams cũng được chuyển sang bất đồng bộ khi sử dụng trong generateMetadataLayout

Typescript

// Trước: đồng bộ
type Params = { slug: string }
 
export function generateMetadata({ params }: { params: Params }) {
  const { slug } = params
}
 
export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Params
}) {
  const { slug } = params
}
 
// Sau: bất đồng bộ
type Params = Promise<{ slug: string }>
 
export async function generateMetadata({ params }: { params: Params }) {
  const { slug } = await params
}
 
export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Params
}) {
  const { slug } = await params
}

Bạn có thể thấy trong version next 15, bạn cần sử dụng await với params để truy cập dữ liệu, nhằm tối ưu hóa hiệu suất.

  • Synchronous Layout

Khi bạn cần xử lý params bất đồng bộ trong thành phần đồng bộ, bạn có thể dùng hook use từ React. Dùng use giúp đơn giản hóa việc xử lý bất đồng bộ dễ dàng hơn.

Typescript

// Trước
type Params = { slug: string }
 
export default function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Params
}) {
  const { slug } = params
}
 
// Sau
import { use } from 'react'
 
type Params = Promise<{ slug: string }>
 
export default function Layout(props: {
  children: React.ReactNode
  params: Params
}) {
  const params = use(props.params)
  const slug = params.slug
}
  • Asynchronous Page

Khi làm việc với các tham số params và searchParams dưới dạng bất đồng bộ trong một page

Typescript

// Trước: params và searchParams được truyền đồng bộ vào hàm generateMetadata
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
 
export function generateMetadata({
  params,
  searchParams,
}: {
  params: Params
  searchParams: SearchParams
}) {
  const { slug } = params
  const { query } = searchParams
}
 
export default async function Page({
  params,
  searchParams,
}: {
  params: Params
  searchParams: SearchParams
}) {
  const { slug } = params
  const { query } = searchParams
}
 
// Sau: params và searchParams là Promise, cần await để truy cập dữ liệu.
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
 
export async function generateMetadata(props: {
  params: Params
  searchParams: SearchParams
}) {
  const params = await props.params
  const searchParams = await props.searchParams
  const slug = params.slug
  const query = searchParams.query
}
 
export default async function Page(props: {
  params: Params
  searchParams: SearchParams
}) {
  const params = await props.params
  const searchParams = await props.searchParams
  const slug = params.slug
  const query = searchParams.query
}
  • Synchronous Page

Typescript

'use client'
 
// Trước: params và searchParams được truyền trực tiếp vào thành phần mà không cần xử lý thêm
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
 
export default function Page({
  params,
  searchParams,
}: {
  params: Params
  searchParams: SearchParams
}) {
  const { slug } = params
  const { query } = searchParams
}
 
// Sau: params và searchParams có thể là bất đồng bộ, vì vậy sử dụng hook `use` từ React để xử lý ngay.
import { use } from 'react'
 
type Params = Promise<{ slug: string }>
type SearchParams = { [key: string]: string | string[] | undefined }
 
export default function Page(props: {
  params: Params
  searchParams: SearchParams
}) {
  const params = use(props.params)
  const searchParams = use(props.searchParams)
  const slug = params.slug
  const query = searchParams.query
}

Bạn có thể không cần định nghĩa type Params cho params và searchParams, giúp code dễ đọc trong TypeScript khi các giá trị cần xử lý bất đồng bộ.

Bạn hoàn toàn có thể viết ngắn gọn hơn không cần xác định kiểu, đơn giản hóa việc xử lý bằng cách truyền props trực tiếp vào use, phù hợp khi không cần kiểm soát kiểu dữ liệu chặt chẽ.

Typescript

// Trước
export default function Page({ params, searchParams }) {
  const { slug } = params
  const { query } = searchParams
}
 
// Sau
import { use } from "react"
 
export default function Page(props) {
  const params = use(props.params)
  const searchParams = use(props.searchParams)
  const slug = params.slug
  const query = searchParams.query
}
 
  • Route Handlers

Trước đây, params là đối tượng đồng bộ, có thể truy cập trực tiếp từ segmentData.params. Ở version 15, params trở thành Promise, vì vậy cần sử dụng await để lấy giá trị từ segmentData.params.

Typescript

// Trước
type Params = { slug: string }
 
export async function GET(request: Request, segmentData: { params: Params }) {
  const params = segmentData.params
  const slug = params.slug
}
 
// Sau
type Params = Promise<{ slug: string }>
 
export async function GET(request: Request, segmentData: { params: Params }) {
  const params = await segmentData.params
  const slug = params.slug
}

Bạn có thể ngắn gọn hơn, không cần phải định nghĩa kiểu dữ liệu:

Typescript

// Trước
export async function GET(request, segmentData) {
  const params = segmentData.params
  const slug = params.slug
}
 
// Sau
export async function GET(request, segmentData) {
  const params = await segmentData.params
  const slug = params.slug
}

3. fetch requests

Trong Next.js 15, mặc định các fetch request không còn được cache để giảm thiểu lưu trữ không cần thiết. Nếu bạn muốn cache thì đơn giản chỉ cần thêm option cache: 'force-cache' vào lệnh fetch. Ví dụ:

Typescript

const lab = await fetch('https://...', { cache: 'force-cache' }) // Cached

Còn nếu như bạn muốn cache mọi yêu cầu trong layout hoặc page, thêm export const fetchCache = 'default-cache' ở trên cùng layout. Ví dụ:

Typescript

export const fetchCache = 'default-cache'
 
export default async function RootLayout() {
  const a = await fetch('https://...') // Cached
  const b = await fetch('https://...', { cache: 'no-store' }) // Not cached
 
  // ...
}

Đây là những thay đổi quan trọng trong Next.js 15 mà cá nhân mình đánh giá cao! Nếu bạn thấy hứng thú, hãy khám phá các cải tiến trong version này.

4. Kết luận

Next.js 15 mang đến một loạt các cải tiến, từ việc nâng cấp hiệu suất với Turbopack cho đến các cập nhật API bất đồng bộ giúp tối ưu hóa dữ liệu và tốc độ phản hồi.

Việc hỗ trợ React 19 cùng các hooks mới là bước tiến nhằm tận dụng tối đa các tính năng mới của React, tối ưu hiệu năng cho các ứng dụng lớn. Next 15 sẽ không làm bạn thất vọng, hãy tự mình trải nghiệm để hiểu rõ hơn các tính năng phù hợp cho dự án của riêng bạn.

Các bài viết liên quan:

  • NextJS là gì? Kiến thức NextJS cơ bản bạn cần biết
  • WebAssembly là gì? Khi nào nên sử dụng WebAssembly
  • State Management trong React: Context API, Redux, Recoil, React Query, Zustand
  • Storybook là gì? Tìm hiểu công cụ Quản lý UI Component
Bài viết cùng chuyên mục
Negative SEO là gì? Cách kẻ xấu phá hoại website bạn trên Google và cách xử lý
Công Nghệ

Negative SEO là gì? Cách kẻ xấu phá hoại website bạn trên Google và cách xử lý

Negative SEO là hình thức chơi xấu trong SEO, khi một bên...

Tối ưu ứng dụng với cấu trúc dữ liệu cơ bản và bitwise
Công Nghệ

Tối ưu ứng dụng với cấu trúc dữ liệu cơ bản và bitwise

Trong bài viết này, 200Lab sẽ chia sẻ những trường hợp dễ...

So sánh Flutter vs React Native: Framework nào đáng học năm 2021
Công Nghệ

So sánh Flutter vs React Native: Framework nào đáng học năm 2021

Điểm chung của Flutter, React Native đều là Cross-platform Mobile, build native...

HTTP/2 là gì? So sánh HTTP/2 và HTTP/1
Công Nghệ

HTTP/2 là gì? So sánh HTTP/2 và HTTP/1

Từ khi Internet ra đời, sự phát triển về các giao thức...

Upload File từ Frontend đến Backend mà rất nhiều bạn vẫn đang làm sai!!
Công Nghệ

Upload File từ Frontend đến Backend mà rất nhiều bạn vẫn đang làm sai!!

1. Client encode file (base64) rồi gởi về backend 200Lab đã từng...

React Native – Hướng dẫn làm việc với Polyline và Animated-Polyline trên Map
Công Nghệ

React Native – Hướng dẫn làm việc với Polyline và Animated-Polyline trên Map

Vẽ đường đi trên bản đồ là một nghiệp vụ vô cùng...

Hybrid App và Native App: Những khác biệt to lớn
Công Nghệ

Hybrid App và Native App: Những khác biệt to lớn

Bất cứ khi nào một công ty quyết định làm ứng dụng...

Web/System Architecture 101 – Kiến trúc web/hệ thống cơ bản cho người mới
Công Nghệ

Web/System Architecture 101 – Kiến trúc web/hệ thống cơ bản cho người mới

Đây là một kiến trúc cơ bản mà bất kì một người...