Đế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@14
và create-next-app@latest
và quan sát 2 file package.json
của 2 phiên bản sau nha:
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ó keypending
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
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.
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.
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é.
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
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
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 generateMetadata
và Layout
// 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.
// 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
// 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
'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ẽ.
// 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.
// 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:
// 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ụ:
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ụ:
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:
Bài viết liên quan
React Toastify là gì? Hướng dẫn sử dụng Toast Notification với React Toastify
Nov 21, 2024 • 7 min read
Hướng dẫn sử dụng Zustand trong NextJS
Nov 21, 2024 • 8 min read
Lazy Loading: Kỹ thuật Tối ưu Hiệu suất Website
Nov 17, 2024 • 14 min read
Hướng dẫn sử dụng Redux Toolkit và Redux Saga trong dự án React
Nov 15, 2024 • 10 min read
WebGL là gì? Hướng dẫn tạo đồ họa đơn giản với WebGL
Nov 13, 2024 • 7 min read
Test-Driven Development (TDD) là gì? Hướng dẫn thực hành TDD
Nov 13, 2024 • 6 min read