npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

leaf-seo

v0.0.1

Published

SEO helper utilities for Leaf framework - Generate meta tags, Open Graph, Twitter Cards, and JSON-LD schema

Readme

🌿 Leaf SEO

Công cụ hỗ trợ SEO cho Leaf framework - Tạo meta tags, Open Graph, Twitter Cards và JSON-LD schema.

Version License TypeScript Bun

Tiếng Việt | English

📦 Cài Đặt

npm install leaf-seo

hoặ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 &lt;, & thành &amp;, 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>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</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 generateMetaTagsgenerateSchemaScripts trong 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.