SEO với Yugen UI
Component Yugen render HTML chuẩn ở phía server, nên công cụ tìm kiếm đọc được nội dung. Phần còn lại là metadata của trang, cấu trúc tiêu đề, link thật và văn bản thay thế cho ảnh.
Nội dung có trong HTML
Client component vẫn render ở server; thứ bot không thấy là nội dung chỉ xuất hiện sau tương tác.
Trong Next.js App Router, component có "use client" vẫn được render thành HTML ở server rồi mới hydrate. Văn bản trong Card, Table, Accordion… nằm sẵn trong HTML gửi về. Khác biệt nằm ở nội dung chỉ được tạo khi người dùng thao tác:
| Component | Có trong HTML ban đầu |
|---|---|
| Accordion | Có — panel đóng vẫn được render (ẩn), bot đọc được. |
| Tabs | Chỉ tab đang chọn. Nội dung quan trọng cho SEO nên đặt ở tab mặc định hoặc thành trang riêng. |
| Dialog, Drawer, Popover, Menu, Tooltip | Không — render khi mở, qua portal. Đừng đặt nội dung chính ở đây. |
| Carousel | Có — mọi slide nằm trong HTML. |
| DataGrid, Table | Có, với dữ liệu có sẵn lúc render ở server (không phải dữ liệu tải sau ở client). |
Cách dựng Next.js với Yugen ở trang Server-Side Rendering & Next.js.
Title và description
Mỗi trang một title và một description riêng, viết cho người đọc kết quả tìm kiếm.
// app/pricing/page.tsx — Server Component
import type { Metadata } from "next";
import { Button, Card } from "yugen-ui";
export const metadata: Metadata = {
title: "Bảng giá · Acme",
description: "Ba gói cho nhóm từ 1 đến 500 người, dùng thử 14 ngày.",
alternates: { canonical: "https://acme.vn/pricing" },
openGraph: { title: "Bảng giá · Acme", images: ["/og/pricing.png"] },
};
export default function PricingPage() {
return (
<main>
<h1 className="text-title1 font-semibold">Bảng giá</h1>
{/* Card, Button… là client component nhưng vẫn render ra HTML ở server */}
</main>
);
}Trang có nội dung động (bài viết, sản phẩm) dùng generateMetadata:
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const post = await getPost((await params).slug);
return { title: `${post.title} · Acme`, description: post.excerpt };
}Vite (SPA) không có metadata theo trang ở server. Trang cần lên kết quả tìm kiếm thì prerender lúc build hoặc dùng framework có SSR; template Blog của create-yugen-ui ghi sẵn title, description và Open Graph cho từng bài lúc build.
Cấu trúc tiêu đề
Một h1 cho mỗi trang, các cấp tiếp theo không nhảy bậc.
Cỡ chữ và cấp tiêu đề là hai việc riêng: chọn thẻ theo cấu trúc (h1–h3), chọn cỡ bằng type ramp (text-title1, text-subtitle1). Một số component tự render thẻ tiêu đề:
| Component | Thẻ |
|---|---|
| DialogTitle | h2 |
| DrawerHeaderTitle | h2 |
| EmptyTitle | h3 |
| Card, CardHeader | Không tự tạo thẻ tiêu đề — tự đặt h2 / h3 phù hợp vị trí. |
Link bot đọc được
Điều hướng bằng thẻ a có href; không dùng onClick trên nút để chuyển trang.
import NextLink from "next/link";
import { Button, Link } from "yugen-ui";
// Link của Yugen là <a href> — bot đọc được.
<Link href="/docs">Tài liệu</Link>
// Dùng router của framework mà vẫn giữ giao diện Yugen: asChild.
<Link asChild>
<NextLink href="/pricing">Bảng giá</NextLink>
</Link>
// Nút trông như Button nhưng là link điều hướng.
<Button appearance="primary" asChild>
<NextLink href="/signup">Đăng ký</NextLink>
</Button>asChild giữ class và trạng thái focus của Yugen nhưng để framework render thẻ a (prefetch, chuyển trang phía client). Bot chỉ đi theo href, nên <Button onClick={() => router.push(...)}> là một trang không ai tìm thấy.
Ảnh và avatar
alt mô tả nội dung ảnh; ảnh trang trí để alt rỗng.
import { Avatar, Image } from "yugen-ui";
<Image src="/team.jpg" alt="Nhóm sản phẩm Acme tại văn phòng Đà Nẵng" width={640} height={360} />
<Image src="/wave.svg" alt="" /> {/* ảnh trang trí: alt rỗng */}
<Avatar name="Nguyễn Minh Anh" src="/u/anh.jpg" /> {/* role="img", tên đọc là name */}Đặt width và height (hoặc khung AspectRatio) để trình duyệt giữ chỗ trước khi ảnh tải xong, tránh nhảy bố cục (CLS). Nút chỉ có icon cần aria-label; đó cũng là chữ duy nhất bot đọc được ở nút đó.
Kiểm tra trước khi phát hành
- Mỗi trang có title và description riêng, canonical khi một nội dung có nhiều URL.
- Nội dung chính nằm trong HTML ban đầu, không ẩn sau Tabs khác tab mặc định, Dialog hay Popover.
- Một h1, các cấp tiêu đề theo thứ tự; mọi ảnh có alt.
- Điều hướng bằng a href (Link, hoặc asChild với link của framework).
- Chuyển trang bằng onClick trên Button.
- Chọn thẻ tiêu đề theo cỡ chữ muốn có.
- Để alt trống cho ảnh có nội dung, hoặc nhồi từ khoá vào alt.