Trong thế giới phát triển web hiện đại, tốc độ không chỉ là một lợi thế, mà là yếu tố sống còn. Một trang web tải chậm không chỉ khiến người dùng thất vọng bỏ đi mà còn bị các công cụ tìm kiếm như Google "ghẻ lạnh", ảnh hưởng trực tiếp đến thứ hạng SEO và doanh thu. May mắn thay, với Next.js, chúng ta được trang bị những công cụ cực kỳ mạnh mẽ để giải quyết triệt để các vấn đề hiệu năng cốt lõi.
Bài viết này sẽ đi sâu vào ba "hung thần" gây chậm trang web: Hình ảnh, Font chữ, và các đoạn mã Script từ bên thứ ba. Chúng ta sẽ khám phá cách "thuần hóa" chúng bằng các component chuyên dụng của Next.js: next/image
, next/font
, và next/script
. Hãy sẵn sàng để đưa ứng dụng Next.js của bạn lên một tầm cao mới về tốc độ và trải nghiệm người dùng! ⚡️
1. next/image: "Phù thủy" tối ưu hóa hình ảnh 🖼️
Hình ảnh thường là tài sản nặng nhất trên một trang web. Tải một bức ảnh 5MB cho màn hình điện thoại là một sự lãng phí tài nguyên khủng khiếp. Component next/image
ra đời để giải quyết vấn đề này một cách thông minh và tự động. Nó không chỉ là một thẻ <img>
thông thường, mà là một cỗ máy tối ưu hóa hình ảnh toàn diện.
Vấn đề cố hữu của thẻ <img>
truyền thống
Thẻ <img>
có khá nhiều điểm yếu nếu không được tối ưu lại:
- Tải kích thước gốc: Tải một ảnh 4K cho một
div
rộng 300px, gây lãng phí băng thông. - Không có lazy loading mặc định: Tất cả hình ảnh đều được tải ngay lập tức, kể cả những ảnh cuối trang, làm chậm quá trình tải ban đầu.
- Layout Shift (CLS): Trình duyệt không biết kích thước ảnh cho đến khi nó được tải xong, gây ra tình trạng "nhảy trang" khó chịu khi nội dung bị đẩy xuống.
- Không tự động chuyển đổi định dạng: Không tận dụng được các định dạng ảnh hiện đại như WebP hay AVIF có khả năng nén tốt hơn.
Sức mạnh của next/image
next/image
giải quyết tất cả các vấn đề trên một cách "thần kỳ":
- Tự động Resizing: Next.js tự động tạo ra nhiều phiên bản của một ảnh với các kích thước khác nhau và chỉ phục vụ phiên bản phù hợp nhất cho thiết bị của người dùng.
- Lazy Loading mặc định: Hình ảnh sẽ chỉ được tải khi người dùng cuộn đến gần chúng, giúp tăng tốc độ tải trang ban đầu một cách đáng kể.
- Chống Layout Shift: Bằng cách yêu cầu
width
vàheight
,next/image
sẽ giữ chỗ cho hình ảnh trước khi nó được tải, loại bỏ hoàn toàn chỉ số Cumulative Layout Shift (CLS). - Định dạng ảnh thế hệ mới: Tự động phục vụ ảnh dưới định dạng WebP hoặc AVIF nếu trình duyệt hỗ trợ, giúp giảm kích thước file mà vẫn giữ nguyên chất lượng.
- Ưu tiên tải (Priority Loading): Đối với những hình ảnh quan trọng ở màn hình đầu tiên (above-the-fold) như hero banner, bạn có thể thêm prop
priority
để báo cho Next.js tải nó trước, cải thiện mạnh mẽ chỉ số Largest Contentful Paint (LCP).
Cách sử dụng hiệu quả next/image
Với ảnh local (trong thư mục public
hoặc src
):
import Image from 'next/image'
import heroBanner from '../public/images/hero-banner.jpg'
function HomePage() {
return (
<Image
src={heroBanner}
alt="Mô tả hình ảnh"
width={1200}
height={600}
priority // Ưu tiên tải cho ảnh LCP
placeholder="blur" // Hiệu ứng mờ khi ảnh đang tải
// sizes giúp trình duyệt chọn ảnh phù hợp với các kích cỡ màn hình khác nhau
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
)
}
Với ảnh từ nguồn bên ngoài (CDN):
Trước tiên, bạn cần cấu hình domain của ảnh trong next.config.js
:
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
port: '',
pathname: '/**',
},
],
},
}
Sau đó sử dụng trong component:
import Image from 'next/image'
function PostCard({ post }) {
return (
<Image
src={post.imageUrl} // "https://images.unsplash.com/photo-..."
alt={post.title}
width={500}
height={300}
loading="lazy" // Mặc định, nhưng có thể ghi rõ
/>
)
}
💡 Mẹo hay: Luôn cung cấp
width
vàheight
chính xác. Sử dụng propsizes
để tối ưu hóa việc chọn ảnh trên các thiết bị khác nhau và dùngpriority
cho hình ảnh quan trọng nhất trên trang.
2. next/font: Giải pháp Font chữ không còn Layout Shift ✍️
Web font mang lại tính thẩm mỹ cao, nhưng chúng cũng là một nguồn gây ra các vấn đề về hiệu năng như Flash of Invisible Text (FOIT) hoặc Flash of Unstyled Text (FOUT), và tệ hơn là Layout Shift khi font chữ được tải xong và thay thế font hệ thống.
next/font
là một cuộc cách mạng trong việc quản lý font. Nó tự động tối ưu hóa và tự host (self-host) các font chữ cho bạn, loại bỏ hoàn toàn các vấn đề trên.
Lợi ích vượt trội của next/font
Những lý do mà bạn nên sử dụng next/font
ngay cho dự án của mình:
- Tự động Self-hosting: Thay vì phải gửi yêu cầu đến Google Fonts mỗi lần tải trang,
next/font
sẽ tải font về và phục vụ nó cùng với các tài sản tĩnh khác của bạn. Điều này loại bỏ một yêu cầu mạng khứ hồi (round-trip), giúp giảm thời gian chặn hiển thị (render-blocking). - Zero Layout Shift: Đây là tính năng "ăn tiền" nhất.
next/font
sử dụng thuộc tính CSSsize-adjust
để điều chỉnh font hệ thống dự phòng sao cho nó chiếm không gian y hệt như web font sẽ tải. Kết quả? Khi web font được tải xong, không có bất kỳ sự xê dịch nào xảy ra. - Hiệu năng cao:
next/font
tự động thêmfont-display: optional;
(hoặcswap
) vào CSS, giúp trình duyệt hiển thị văn bản ngay lập tức bằng font dự phòng.
Cách sử dụng next/font
Với Google Fonts:
// In your layout.js or _app.js
import { Inter, Roboto_Mono } from 'next/font/google'
// Khởi tạo font với các tùy chọn
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter', // Sử dụng với CSS Variables
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}
Sau đó trong file globals.css
của bạn:
body {
font-family: var(--font-inter), sans-serif;
}
h1,
h2,
code {
font-family: var(--font-roboto-mono), monospace;
}
Với Font Local:
// In your layout.js or _app.js
import localFont from 'next/font/local'
const myFont = localFont({
src: './MyCustomFont.woff2',
display: 'swap',
variable: '--font-my-custom',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={myFont.variable}>
<body>{children}</body>
</html>
)
}
💡 Mẹo hay: Luôn sử dụng
next/font
thay vì thẻ<link>
truyền thống trong_document.js
để nhúng font. Nó mang lại lợi ích về hiệu năng và trải nghiệm người dùng mà không cần nỗ lực cấu hình phức tạp.
3. next/script: "Nhạc trưởng" quản lý Script bên thứ ba 📜
Các đoạn mã script từ bên thứ ba (analytics, chatbot, advertisement, Google Tag Manager) là một trong những thủ phạm hàng đầu làm chậm website và ảnh hưởng đến chỉ số Total Blocking Time (TBT). Chúng thường chặn luồng chính (main thread), làm trì hoãn khả năng tương tác của người dùng.
Component next/script
cho phép bạn kiểm soát chính xác thời điểm và cách thức các script này được tải và thực thi.
Các chiến lược tải (Loading Strategies)
next/script
cung cấp một prop strategy
cực kỳ mạnh mẽ:
-
strategy="beforeInteractive"
:- Khi nào dùng: Chỉ dành cho các script cực kỳ quan trọng phải được thực thi trước khi trang có thể tương tác, ví dụ như script quản lý cookie consent.
- Lưu ý: Sử dụng rất cẩn thận vì nó có thể chặn hiển thị trang.
-
strategy="afterInteractive"
(Mặc định):- Khi nào dùng: Lựa chọn hoàn hảo cho hầu hết các script. Script sẽ được tải và thực thi ngay sau khi trang đã có thể tương tác (hydrated).
- Ví dụ: Google Analytics, Google Tag Manager.
-
strategy="lazyOnload"
:- Khi nào dùng: Dành cho các script có độ ưu tiên thấp, có thể chờ để tải sau khi tất cả các tài nguyên khác đã tải xong và trình duyệt đang "rảnh rỗi".
- Ví dụ: Chatbot (Intercom, Crisp), các widget mạng xã hội.
Cách sử dụng next/script
import Script from 'next/script'
function MyPage() {
return (
<div>
<h1>My Page</h1>
{/* Google Tag Manager - Tải sau khi trang tương tác */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive"
/></Script>
{/* Intercom Chat Widget - Tải khi trình duyệt rảnh */}
<Script
src="https://widget.intercom.io/widget/APP_ID"
strategy="lazyOnload"
/>
)
}
Tối ưu hóa nâng cao với Web Workers (Partytown)
Đối với các script nặng, bạn có thể đẩy chúng ra khỏi luồng chính hoàn toàn bằng cách chạy chúng trong một Web Worker. Next.js tích hợp với Partytown để làm điều này một cách dễ dàng.
Để kích hoạt, trong next.config.js
:
// next.config.js
module.exports = {
experimental: {
workerThreads: true,
cpus: 1, // Giới hạn số lượng worker để tránh tiêu tốn tài nguyên
},
}
Sau đó sử dụng strategy="worker"
:
<Script src="https://example.com/heavy-script.js" strategy="worker" />
Hành động này sẽ chuyển gánh nặng xử lý của script đó sang một luồng nền, giữ cho giao diện người dùng của bạn luôn mượt mà và phản hồi nhanh chóng.
Kết luận: Tối ưu hóa không phải việc làm một lần
Việc tối ưu hóa không phải là một công việc làm một lần rồi thôi, mà là một quá trình liên tục. Tuy nhiên, bằng cách nắm vững và áp dụng ba "vũ khí" mạnh mẽ mà Next.js cung cấp:
next/image
: Để phục vụ hình ảnh nhanh, nhẹ và không gây layout shift.next/font
: Để có font chữ đẹp mà không ảnh hưởng đến hiệu năng và trải nghiệm người dùng.next/script
: Để kiểm soát các script của bên thứ ba và giữ cho luồng chính luôn thông thoáng.
Bạn đã có trong tay bộ công cụ cần thiết để xây dựng những ứng dụng web không chỉ đẹp về giao diện, mạnh về tính năng mà còn siêu nhanh về tốc độ. Hãy bắt đầu kiểm tra lại dự án của mình ngay hôm nay và áp dụng những kỹ thuật này, bạn sẽ thấy sự khác biệt rõ rệt trên các công cụ đo lường như PageSpeed Insights và quan trọng hơn cả là trong sự hài lòng của người dùng cuối.