leaf-seo
v0.0.1
Published
SEO helper utilities for Leaf framework - Generate meta tags, Open Graph, Twitter Cards, and JSON-LD schema
Maintainers
Readme
🌿 Leaf SEO
Công cụ hỗ trợ SEO cho Leaf framework - Tạo meta tags, Open Graph, Twitter Cards và JSON-LD schema.
Tiếng Việt | English
📦 Cài Đặt
npm install leaf-seohoặc
bun add leaf-seo🚀 Bắt Đầu Nhanh
Cách Sử Dụng Cơ Bản
import { generateMetaTags, generateSchemaScripts, renderHead } from "leaf-seo";
const options = {
title: "Trang Web Tuyệt Vời",
description: "Mô tả về trang web tuyệt vời của tôi",
image: "https://example.com/image.jpg",
url: "https://example.com/trang",
type: "website",
siteName: "Website Của Tôi",
};
const metaTags = generateMetaTags(options);
const schemaScripts = generateSchemaScripts(options);
const html = renderHead(metaTags, schemaScripts, options.title);📖 Tài Liệu
generateMetaTags
Tạo mảng các meta tags bao gồm:
- Meta tags cơ bản (charset, viewport, description, keywords, robots)
- Open Graph tags
- Twitter Card tags
- Link tags (canonical, favicon, apple-touch-icon)
import { generateMetaTags, type MetaOptions } from "leaf-seo";
const options: MetaOptions = {
title: "Tiêu Đề Trang",
description: "Mô tả trang",
image: "https://example.com/og-image.jpg",
url: "https://example.com/trang",
type: "website",
siteName: "Trang Web Của Tôi",
keywords: ["từ khóa 1", "từ khóa 2"],
locale: "vi_VN",
twitterCard: "summary_large_image",
robots: "index, follow",
themeColor: "#ffffff",
validateLength: true, // Cảnh báo nếu title/description quá dài
};
const tags = generateMetaTags(options);Validation & Strict Mode
Mặc định, generateMetaTags chỉ cảnh báo cho dữ liệu không hợp lệ. Bật strict mode để throw errors:
import { generateMetaTags, SEOValidationError } from "leaf-seo";
try {
const tags = generateMetaTags(options, { strict: true });
} catch (error) {
if (error instanceof SEOValidationError) {
console.error("Validation thất bại:", error.message);
}
}Strict mode kiểm tra:
- Required fields (title, description, image, url, siteName)
- URL format (phải là URL http/https hợp lệ)
- Product fields (price, currency, brand, availability, condition)
- Article fields (author, publishedDate phải là ISO 8601)
- Review ratings (phải từ 1-5)
- Độ dài title/description
Custom Meta Tags
Thêm custom meta tags vào các tags đã generate:
const customTags = [
{ name: "custom-meta", content: "custom-value" },
{ property: "custom:property", content: "custom-property-value" },
];
const tags = generateMetaTags(options, { customTags });generateSchemaScripts
Tạo các script JSON-LD structured data cho:
- BreadcrumbList (tùy chọn)
- Product schema
- Article schema
- Website schema
- Profile schema
import { generateSchemaScripts, type MetaOptions, type BreadcrumbItem } from "leaf-seo";
const options: MetaOptions = {
// ... các tùy chọn meta
type: "product",
};
const breadcrumbItems: BreadcrumbItem[] = [
{ position: 1, name: "Trang Chủ", id: "https://example.com/" },
{ position: 2, name: "Sản Phẩm", id: "https://example.com/san-pham" },
{ position: 3, name: "Tên Sản Phẩm", id: "https://example.com/san-pham/123" },
];
const schemas = generateSchemaScripts(options, breadcrumbItems);Với strict validation:
try {
const schemas = generateSchemaScripts(options, breadcrumbItems, { strict: true });
} catch (error) {
if (error instanceof SEOSchemaError) {
console.error("Schema validation thất bại:", error.message);
}
}renderHead
Render meta tags và schema scripts thành chuỗi HTML.
HTML được tự động escape để tránh XSS:
import { renderHead } from "leaf-seo";
const html = renderHead(metaTags, schemaScripts, "Tiêu Đề Trang");
// Tất cả HTML entities trong content được escape: < thành <, & thành &, etc.Tắt HTML escaping (chỉ dùng khi tin tưởng nguồn content):
// Chỉ tắt nếu bạn tin tưởng nguồn content
const html = renderHead(metaTags, schemaScripts, "Tiêu Đề Trang", {
escapeHtml: false,
});🎯 Các Trường Hợp Sử Dụng
1. Trang Website
const websiteOptions: MetaOptions = {
title: "Chào Mừng Đến Website Của Tôi",
description: "Website tốt nhất từ trước đến nay",
image: "https://example.com/og-image.jpg",
url: "https://example.com",
type: "website",
siteName: "Website Của Tôi",
locale: "vi_VN",
validateLength: true,
};
const metaTags = generateMetaTags(websiteOptions);
const schemas = generateSchemaScripts(websiteOptions);2. Trang Sản Phẩm
const productOptions: MetaOptions = {
title: "Samsung Galaxy S24 Ultra giảm đến 5Tr, lên đời trợ giá 2Tr",
description: "Mua Samsung Galaxy S24 Ultra chính hãng - Sẵn hàng giá rẻ",
image: "https://example.com/product.jpg",
url: "https://example.com/san-pham/s24-ultra",
type: "product",
siteName: "Cửa Hàng Của Tôi",
// Các trường dành cho sản phẩm
price: 22590000,
currency: "VND",
availability: "InStock",
condition: "NewCondition",
brand: "Samsung",
sku: "samsung-galaxy-s24-ultra",
reviews: [
{ author: "Nguyễn Văn A", rating: 5 },
{ author: "Trần Thị B", rating: 4 },
],
aggregateRating: { ratingValue: 4.5, reviewCount: 10 },
};
const metaTags = generateMetaTags(productOptions);
const schemas = generateSchemaScripts(productOptions);3. Trang Bài Viết/Blog
const articleOptions: MetaOptions = {
title: "Hướng Dẫn Sử Dụng Leaf SEO",
description: "Hướng dẫn chi tiết về cách sử dụng package Leaf SEO",
image: "https://example.com/article.jpg",
url: "https://example.com/bai-viet/huong-dan-leaf-seo",
type: "article",
siteName: "Blog Của Tôi",
// Các trường dành cho bài viết
author: "Nguyễn Văn A",
publishedDate: "2025-01-15T10:00:00Z",
modifiedDate: "2025-01-16T15:30:00Z",
};
const metaTags = generateMetaTags(articleOptions);
const schemas = generateSchemaScripts(articleOptions);4. Trang Hồ Sơ
const profileOptions: MetaOptions = {
title: "Nguyễn Văn A - Lập Trình Viên",
description: "Full-stack developer và người đóng góp mã nguồn mở",
image: "https://example.com/profile.jpg",
url: "https://example.com/ho-so/nguyen-van-a",
type: "profile",
siteName: "Website Của Tôi",
};
const metaTags = generateMetaTags(profileOptions);
const schemas = generateSchemaScripts(profileOptions);🛡️ Xử Lý Lỗi
Error Classes
Package export các error classes tùy chỉnh để xử lý lỗi tốt hơn:
import {
SEOValidationError,
SEOSchemaError,
} from "leaf-seo";
try {
const tags = generateMetaTags(invalidOptions, { strict: true });
} catch (error) {
if (error instanceof SEOValidationError) {
// Xử lý lỗi validation
console.error("Lỗi validation:", error.message);
} else if (error instanceof SEOSchemaError) {
// Xử lý lỗi schema
console.error("Lỗi schema:", error.message);
}
}Type Guards
Kiểm tra type-safe cho các option types:
import { isProductOptions, isArticleOptions } from "leaf-seo";
if (isProductOptions(options)) {
// TypeScript biết options có price, brand, etc.
console.log(options.price, options.brand);
}
if (isArticleOptions(options)) {
// TypeScript biết options có author, publishedDate
console.log(options.author, options.publishedDate);
}🔧 Định Nghĩa Kiểu Dữ Liệu
BaseMetaConfig
Config cơ sở để kế thừa, chỉ chứa các field optional và common:
type BaseMetaConfig = Partial<{
siteName: string;
keywords: string[];
locale: string;
twitterCard: "summary" | "summary_large_image" | "app" | "player";
fbAppId: string;
robots: string;
themeColor: string;
favicon: string;
canonical: boolean;
appleTouchIcon: string;
validateLength: boolean;
}>;MetaOptions
type BaseMetaOptions = {
title: string; // Bắt buộc, khuyến nghị <60 ký tự
description: string; // Bắt buộc, khuyến nghị 150-160 ký tự
image: string; // Bắt buộc, URL tuyệt đối, kích thước >=1200x630
url: string; // Bắt buộc, URL chuẩn
type: "website" | "article" | "product" | "profile";
siteName: string;
keywords?: string[];
locale?: string; // Mặc định: "vi_VN"
twitterCard?: "summary" | "summary_large_image" | "app" | "player";
fbAppId?: string;
robots?: string; // Mặc định: "index, follow"
themeColor?: string;
favicon?: string; // Mặc định: "/favicon.ico"
canonical?: boolean; // Mặc định: true
appleTouchIcon?: string;
validateLength?: boolean; // Mặc định: false
};
// Loại product yêu cầu ProductOptions
type ProductOptions = {
price: number;
currency: string;
availability: "InStock" | "OutOfStock" | "PreOrder";
condition: "NewCondition" | "UsedCondition";
brand: string;
sku?: string;
mpn?: string;
additionalProperties?: { name: string; value: string }[];
reviews?: { author: string; rating: number }[];
aggregateRating?: { ratingValue: number; reviewCount: number };
};
// Loại article yêu cầu ArticleOptions
type ArticleOptions = {
author: string;
publishedDate: string; // Ngày ISO
modifiedDate?: string; // Ngày ISO
};🔄 Kế Thừa Config (Inheritance)
Bạn có thể định nghĩa một base config với các giá trị chung và kế thừa nó cho các trang khác nhau. Nếu khai báo lại thì sẽ ghi đè giá trị từ base config.
Cách Sử Dụng
import { extendMetaOptions, generateMetaTags, type BaseMetaConfig } from "leaf-seo";
// Định nghĩa base config chung cho toàn bộ website
const baseConfig: BaseMetaConfig = {
siteName: "My Website",
locale: "vi_VN",
favicon: "https://example.com/favicon.ico",
themeColor: "#ffffff",
fbAppId: "123456789",
twitterCard: "summary_large_image",
robots: "index, follow",
canonical: true,
keywords: ["website", "default"],
};
// Sử dụng cho trang cụ thể - chỉ cần khai báo các field bắt buộc và unique
const pageOptions = extendMetaOptions(baseConfig, {
title: "Trang Chủ",
description: "Chào mừng đến với website của chúng tôi",
image: "https://example.com/home.jpg",
url: "https://example.com",
type: "website",
// Các field khác sẽ được kế thừa từ baseConfig
});
const metaTags = generateMetaTags(pageOptions);Override Giá Trị
// Nếu muốn ghi đè một số giá trị từ base config
const productOptions = extendMetaOptions(baseConfig, {
title: "Sản Phẩm",
description: "Mô tả sản phẩm",
image: "https://example.com/product.jpg",
url: "https://example.com/product",
type: "product",
locale: "en_US", // Override locale từ base
keywords: ["product", "specific"], // Override keywords từ base (ghi đè hoàn toàn)
// Các field khác vẫn kế thừa từ baseConfig
price: 100000,
currency: "VND",
availability: "InStock",
condition: "NewCondition",
brand: "Brand Name",
});Ví Dụ Thực Tế
// File: config/seo.ts
export const seoBaseConfig: BaseMetaConfig = {
siteName: "CELLPHONES",
locale: "vi_VN",
favicon: "https://cdn.example.com/favicon.ico",
themeColor: "#ffffff",
fbAppId: "112980886043945",
twitterCard: "summary_large_image",
robots: "index, follow",
canonical: true,
validateLength: true,
};
// File: routes/products/[id]/get.ts
import { extendMetaOptions, generateMetaTags } from "leaf-seo";
import { seoBaseConfig } from "../../../config/seo";
export async function GET({ params }) {
const product = await getProduct(params.id);
const seoOptions = extendMetaOptions(seoBaseConfig, {
title: product.name,
description: product.description,
image: product.image,
url: `https://example.com/products/${product.id}`,
type: "product",
price: product.price,
currency: "VND",
availability: product.inStock ? "InStock" : "OutOfStock",
condition: "NewCondition",
brand: product.brand,
sku: product.sku,
});
const metaTags = generateMetaTags(seoOptions);
// ... render page với metaTags
}🔒 Bảo Mật
HTML Escaping
Mặc định, renderHead escape tất cả HTML content để tránh XSS attacks:
const options = {
title: '<script>alert("xss")</script>',
// ...
};
const html = renderHead(tags, schemas, options.title);
// Output: <title><script>alert("xss")</script></title>Luôn sử dụng escaping mặc định trừ khi bạn hoàn toàn tin tưởng nguồn content.
⚡ Thực Hành Tốt Nhất
1. Độ Dài Tiêu Đề
- Giữ tiêu đề dưới 60 ký tự (hoặc 580 pixels)
- Sử dụng
validateLength: trueđể nhận cảnh báo
2. Độ Dài Mô Tả
- Khuyến nghị: 150-160 ký tự
- Sử dụng
validateLength: trueđể nhận cảnh báo
3. Kích Thước Hình Ảnh
- Sử dụng URL tuyệt đối
- Kích thước khuyến nghị: >=1200x630 pixels
- Định dạng hỗ trợ: JPG, PNG, WebP
4. URL Canonical
- Luôn sử dụng URL tuyệt đối
- Đảm bảo URL là phiên bản chuẩn
5. Structured Data
- Luôn bao gồm breadcrumbs để điều hướng tốt hơn
- Sử dụng các loại schema phù hợp
- Xác thực với Google Rich Results Test
6. Validation
- Sử dụng strict mode trong development để bắt lỗi sớm
- Validate URLs trước khi truyền vào
generateMetaTags - Sử dụng định dạng ISO 8601 cho dates trong article options
7. Error Handling
- Luôn wrap
generateMetaTagsvàgenerateSchemaScriptstrong try-catch khi dùng strict mode - Kiểm tra error types bằng
instanceofđể xử lý lỗi tốt hơn - Log errors phù hợp trong production
🧪 Kiểm Thử
bun test📝 Giấy Phép
ISC
🤝 Đóng Góp
Mọi đóng góp đều được chào đón! Vui lòng gửi Pull Request.
