Next.js 16 from Zero to Senior · Part 8 — Rendering, Metadata, SEO & Assets
Control how pages render (static, dynamic, PPR), generate metadata and OG images, ship sitemaps and robots, and use the built-in optimizations: next/image, next/font, and next/script — for fast, discoverable pages.
Một trang có thể hoàn hảo mà vẫn thất bại nếu Google không đọc được, preview mạng xã hội trống, hay ảnh hero làm giật layout. Phần này nói về tầng “nhanh và dễ tìm” của Next.js: chiến lược render, metadata/SEO, và các tối ưu asset tích hợp.
1. Ba chiến lược render
Mỗi route quy về một trong ba hành vi:
- Tĩnh — prerender lúc build, phục vụ dưới dạng HTML đã cache. Nhanh nhất, rẻ nhất. Dùng cho nội dung giống nhau với mọi người.
- Động — render theo từng request. Dùng khi output phụ thuộc request (cookie, header, dữ liệu mới).
- Partial Prerendering (PPR) — vỏ tĩnh với lỗ động được stream vào (Phần 4). Điểm ngọt mặc định của Next.js 16.
Cái gì làm một route động: đọc cookies(), headers(), searchParams, hoặc dữ liệu không cache. Cái gì giữ nó tĩnh/cache: 'use cache' và generateStaticParams.
┌─────────────── PPR page ───────────────┐
instant → │ static shell ('use cache') │
│ └─ <Suspense> dynamic hole (stream) │ ← personalized
└────────────────────────────────────────┘
Bạn hiếm khi set thủ công nữa — bạn kết hợp bằng 'use cache' và <Suspense>, và Next.js lo phần còn lại.
2. Metadata tĩnh
Export một object metadata từ bất kỳ layout.tsx hay page.tsx:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Pricing — Senior Next',
description: 'Simple, transparent pricing.',
openGraph: {
title: 'Pricing — Senior Next',
description: 'Simple, transparent pricing.',
images: ['/og/pricing.png'],
},
twitter: { card: 'summary_large_image' },
};
Metadata gộp xuống theo cây layout, nên root layout đặt mặc định và page ghi đè.
Mẫu tiêu đề
// app/layout.tsx
export const metadata: Metadata = {
title: {
default: 'Senior Next',
template: '%s · Senior Next', // child pages fill %s
},
};
Một page export title: 'Pricing' trở thành Pricing · Senior Next.
3. Metadata động với generateMetadata
Cho trang dựa trên dữ liệu (bài blog, sản phẩm), tạo metadata bất đồng bộ:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: { images: [post.coverImage] },
};
}
Lời fetch ở đây được dedup với fetch của chính page (Phần 3), nên không tốn thêm request.
4. Metadata theo file: ảnh OG, sitemap, robots
Next.js tạo metadata từ các file đặt tên đặc biệt.
Ảnh OG động
Tạo ảnh preview mạng xã hội ở edge bằng ImageResponse:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function OG({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
return new ImageResponse(
(
<div style={{ fontSize: 64, background: '#0a0a0a', color: '#c8ff00', width: '100%', height: '100%', display: 'flex', alignItems: 'center', padding: 80 }}>
{post.title}
</div>
),
size
);
}
Sitemap & robots
// app/sitemap.ts
import type { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts();
return [
{ url: 'https://example.com', lastModified: new Date() },
...posts.map((p) => ({ url: `https://example.com/blog/${p.slug}`, lastModified: p.updatedAt })),
];
}
// app/robots.ts
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: '*', allow: '/', disallow: '/dashboard/' },
sitemap: 'https://example.com/sitemap.xml',
};
}
Cũng hữu ích: app/manifest.ts (manifest PWA), app/icon.png / app/apple-icon.png (favicon).
5. next/image — hết giật layout
next/image tự động resize, phục vụ định dạng hiện đại (AVIF/WebP), lazy-load, và giữ chỗ để tránh CLS:
import Image from 'next/image';
export function Hero() {
return (
<Image
src="/hero.jpg"
alt="Product hero"
width={1200}
height={600}
priority // preload above-the-fold images; skips lazy-loading
/>
);
}
Quy tắc:
- Luôn cung cấp
width/height(hoặc dùngfillvới cha có kích thước) — để giữ chỗ. - Dùng
prioritychỉ cho ảnh LCP; đừng cho mọi ảnh. - Cho ảnh từ xa, whitelist host trong
next.config.ts:
// next.config.ts
const nextConfig: NextConfig = {
images: { remotePatterns: [{ protocol: 'https', hostname: 'cdn.example.com' }] },
};
6. next/font — font không giật layout
next/font tự host font lúc build, loại bỏ request mạng tới Google và FOUT/CLS đi kèm:
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
Font local dùng next/font/local với cùng lợi ích.
7. next/script — nạp script bên thứ ba an toàn
Kiểm soát khi nào một script bên thứ ba nạp để nó không chặn trang:
import Script from 'next/script';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<Script src="https://example.com/analytics.js" strategy="afterInteractive" />
</>
);
}
Chiến lược: beforeInteractive (quan trọng, hiếm), afterInteractive (mặc định, analytics), lazyOnload (ưu tiên thấp, widget chat).
8. Checklist SEO
- Mỗi trang có
title+descriptionriêng. - Tag OG + Twitter để link preview đẹp.
- Có
sitemap.ts+robots.ts. - URL canonical đặt qua
alternates.canonicalcho trang dễ trùng lặp. - HTML ngữ nghĩa và
<a href>/<Link>thật (crawler theo link, không phảionClick). - Giữ nội dung quan trọng render-server, không fetch client sau khi tải.
- Dùng
generateStaticParamsđể trang quan trọng được prerender.
9. Bài tập
-
Mẫu tiêu đề: đặt
title.templateở root layout vàtitlemỗi page; xác nhận<title>được ghép trong HTML. -
Metadata động: thêm
generateMetadatavào route[slug]lấy title/description từ bài. -
Ảnh OG: thêm
opengraph-image.tsxvào route blog và xem bằng cách vào/blog/<slug>/opengraph-image. -
Sitemap & robots: tạo
sitemap.tstừ các bài vàrobots.tschặn/dashboard. -
Kiểm tra ảnh: thay
<img>thô bằngnext/image, thêmprioritycho ảnh LCP, và xác nhận CLS giảm trong Lighthouse. -
Font: áp dụng
next/font/google, rồi kiểm tra tab Network thấy font tự host (không request tới fonts.googleapis.com).
Phần tiếp theo
Trang của bạn giờ render đúng chiến lược, preview đẹp khi chia sẻ, xếp hạng tốt, và tải nhanh với ảnh và font tối ưu.
Phần 9 xử lý xác thực và bảo mật: session và cookie, Data Access Layer, bảo vệ route và Server Action, dùng proxy.ts để chuyển hướng, và các header cùng thực hành bảo mật mọi app Next.js cần.