Yugen UI

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:

ComponentCó trong HTML ban đầu
AccordionCó — panel đóng vẫn được render (ẩn), bot đọc được.
TabsChỉ 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, TooltipKhông — render khi mở, qua portal. Đừng đặt nội dung chính ở đây.
CarouselCó — mọi slide nằm trong HTML.
DataGrid, TableCó, 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 đề:

ComponentThẻ
DialogTitleh2
DrawerHeaderTitleh2
EmptyTitleh3
Card, CardHeaderKhông tự tạo thẻ tiêu đề — tự đặt h2 / h3 phù hợp vị trí.

Ả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

Nên
  • 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).
Không nên
  • 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.