@oxmo88/next-wordpress-utils
v1.0.0
Published
Comprehensive WordPress/Next.js utilities: SEO, sitemaps, video schemas, URL handling, text processing, and multi-locale support
Downloads
50
Maintainers
Readme
@next-wordpress/utils
Comprehensive WordPress/Next.js utilities for SEO, sitemaps, video schemas, URL handling, text processing, and multi-locale support.
Features
- 🎯 Configuration-Based: All utilities accept a site configuration, making them fully reusable across projects
- 🌍 Multi-Locale Support: Built-in support for multiple languages and locales
- 📱 SEO Optimized: Generate canonical URLs, hreflang tags, OpenGraph metadata, and structured data
- 🎬 Video Schema: Automatic video detection and VideoObject structured data generation
- 📄 Sitemap Utilities: Priority calculation, URL validation, and sitemap splitting
- 🔤 Text Processing: HTML stripping, slug generation, entity decoding, and more
- 📦 Tree-Shakeable: Import only what you need with modular exports
- 🔷 TypeScript: Full type safety with TypeScript definitions
- 📚 Dual Format: Works with both ESM and CommonJS
Installation
pnpm add @next-wordpress/utilsQuick Start
1. Create Your Site Configuration
First, create a configuration file for your site:
// lib/site.config.ts
import { SiteConfig } from '@next-wordpress/utils'
export const siteConfig: SiteConfig = {
baseUrl: 'https://yourdomain.com',
siteName: 'Your Site Name',
defaultLocale: 'en-US',
locales: {
'en-US': '', // Default locale, no prefix
'es-ES': 'es', // Spanish: /es/*
'fr-FR': 'fr', // French: /fr/*
},
pathMappings: {
article: {
'en-US': 'news',
'es-ES': 'noticias',
'fr-FR': 'actualites',
},
page: {
'en-US': 'pages',
'es-ES': 'paginas',
'fr-FR': 'pages',
},
},
metadata: {
title: 'Your Site Title',
description: 'Your site description',
keywords: ['keyword1', 'keyword2'],
author: 'Your Name',
twitter: {
site: '@yourhandle',
creator: '@yourhandle',
},
openGraph: {
type: 'website',
locale: 'en_US',
},
},
}2. Use the Utilities
import { getCanonicalUrl, generatePageMetadata } from '@next-wordpress/utils'
import { siteConfig } from '@/lib/site.config'
// Generate canonical URL
const canonicalUrl = getCanonicalUrl('/about', siteConfig)
// Result: "https://yourdomain.com/about"
// Generate page metadata
const metadata = generatePageMetadata(
{
title: 'About Us',
description: 'Learn about our company',
type: 'website',
},
'/about',
siteConfig
)Modular Imports
Import only what you need to reduce bundle size:
// Import everything
import * as utils from '@next-wordpress/utils'
// Import specific modules
import { generateSeoSlug, stripHtml } from '@next-wordpress/utils/text'
import { getCanonicalUrl } from '@next-wordpress/utils/url'
import { calculateContentPriority } from '@next-wordpress/utils/sitemap'
import { extractVideosFromContent } from '@next-wordpress/utils/video'
// Import SEO convenience module (includes text, url, sitemap utilities)
import {
generateSeoSlug,
getCanonicalUrl,
calculateContentPriority
} from '@next-wordpress/utils/seo'API Reference
Configuration Types
SiteConfig
Main configuration interface for all utilities.
interface SiteConfig {
baseUrl: string // e.g., "https://yourdomain.com"
siteName: string // Site name for Open Graph
defaultLocale: string // e.g., "en-US"
locales: Record<string, string> // { "en-US": "", "es-ES": "es" }
pathMappings?: Record<string, Record<string, string>> // Content type paths per locale
metadata?: {
title?: string
description?: string
keywords?: string[]
author?: string
twitter?: { site?: string; creator?: string }
openGraph?: { type?: string; locale?: string }
}
}MetadataTemplate
Template for page metadata generation.
interface MetadataTemplate {
title: string
description: string
keywords?: string[]
author?: string
type?: 'website' | 'article' | 'product'
image?: string
publishedTime?: string
modifiedTime?: string
section?: string
tags?: string[]
}URL Utilities
getCanonicalUrl()
Generate canonical URL from path and config.
function getCanonicalUrl(path: string = '', config: SiteConfig): string
// Example
const url = getCanonicalUrl('/about', siteConfig)
// Result: "https://yourdomain.com/about"getLanguageAlternates()
Generate language alternate URLs for hreflang tags.
function getLanguageAlternates(
basePath: string = '',
config: SiteConfig
): Record<string, string>
// Example
const alternates = getLanguageAlternates('/about', siteConfig)
// Result: {
// 'en-US': 'https://yourdomain.com/about',
// 'es-ES': 'https://yourdomain.com/es/about',
// 'fr-FR': 'https://yourdomain.com/fr/about',
// 'x-default': 'https://yourdomain.com/about'
// }generatePageMetadata()
Generate complete page metadata using config and template.
function generatePageMetadata(
template: MetadataTemplate,
path: string = '',
config: SiteConfig
)
// Example
const metadata = generatePageMetadata(
{
title: 'About Us',
description: 'Learn about our company',
type: 'website',
image: 'https://yourdomain.com/og-image.jpg',
},
'/about',
siteConfig
)
// Returns Next.js metadata object with:
// - title, description, keywords
// - metadataBase
// - alternates (canonical + hreflang)
// - openGraph
// - twitter cardgetArticleUrl()
Get article URL by slug and locale using config.
function getArticleUrl(
slug: string,
locale: string,
contentType: string,
config: SiteConfig
): string
// Example
const url = getArticleUrl('hello-world-123', 'es-ES', 'article', siteConfig)
// Result: "/es/noticias/hello-world-123"getArticleCanonicalUrl()
Build full canonical URL for article.
function getArticleCanonicalUrl(
slug: string,
locale: string,
contentType: string,
config: SiteConfig
): string
// Example
const url = getArticleCanonicalUrl('hello-world-123', 'es-ES', 'article', siteConfig)
// Result: "https://yourdomain.com/es/noticias/hello-world-123"parseLocaleFromPathname()
Parse locale from pathname.
function parseLocaleFromPathname(
pathname: string,
config: SiteConfig
): string
// Example
const locale = parseLocaleFromPathname('/es/noticias/article', siteConfig)
// Result: "es-ES"getHomeUrl()
Get home page URL for locale.
function getHomeUrl(locale: string, config: SiteConfig): string
// Example
const homeUrl = getHomeUrl('es-ES', siteConfig)
// Result: "/es"buildLocalizedPath()
Build localized path.
function buildLocalizedPath(
path: string,
locale: string,
config: SiteConfig
): string
// Example
const path = buildLocalizedPath('/about', 'es-ES', siteConfig)
// Result: "/es/about"normalizeUrl()
Normalize URL to match config base URL.
function normalizeUrl(url: string, config: SiteConfig): string
// Example
const normalized = normalizeUrl('http://olddomain.com/page', siteConfig)
// Result: "https://yourdomain.com/page"cn()
Tailwind CSS class merger utility (combines clsx + tailwind-merge).
function cn(...inputs: ClassValue[]): string
// Example
const className = cn('px-4', 'py-2', { 'bg-blue-500': isActive })Text Utilities
generateSeoSlug()
Generate SEO-friendly slug from title.
function generateSeoSlug(title: string, id: number): string
// Example
const slug = generateSeoSlug('Héllo World! 🎉', 123)
// Result: "hello-world-123"extractIdFromSlug()
Extract ID from slug.
function extractIdFromSlug(slug: string): number | null
// Example
const id = extractIdFromSlug('hello-world-123')
// Result: 123stripHtml()
Strip HTML tags and decode HTML entities.
function stripHtml(html: string): string
// Example
const text = stripHtml('<p>Hello & welcome!</p>')
// Result: "Hello & welcome!"truncateText()
Truncate text to specified length with ellipsis.
function truncateText(
text: string,
maxLength: number,
suffix: string = '...'
): string
// Example
const truncated = truncateText('This is a long text', 10)
// Result: "This is a..."extractTextFromHtml()
Extract plain text from HTML.
function extractTextFromHtml(html: string): string
// Example
const text = extractTextFromHtml('<p>Hello</p><p>World</p>')
// Result: "Hello World"normalizeWhitespace()
Normalize whitespace in text.
function normalizeWhitespace(text: string): string
// Example
const normalized = normalizeWhitespace('Hello \n World')
// Result: "Hello World"toTitleCase()
Convert text to title case.
function toTitleCase(text: string): string
// Example
const title = toTitleCase('hello world')
// Result: "Hello World"generateExcerpt()
Generate excerpt from text.
function generateExcerpt(
text: string,
maxLength: number = 160,
suffix: string = '...'
): string
// Example
const excerpt = generateExcerpt(longText, 100)Sitemap Utilities
calculateContentPriority()
Calculate dynamic priority and changefreq based on content age.
function calculateContentPriority(modifiedDate: string | Date): {
priority: number
changefreq: ChangeFreq
}
// Example
const { priority, changefreq } = calculateContentPriority('2024-01-15')
// Recent content: { priority: 0.9, changefreq: 'daily' }
// Older content: { priority: 0.5, changefreq: 'monthly' }generateSitemapUrl()
Generate a sitemap URL entry.
interface SitemapRoute {
url: string
lastModified?: string | Date
changeFrequency?: ChangeFreq
priority?: number
alternates?: { languages?: Record<string, string> }
}
function generateSitemapUrl(
path: string,
config: SiteConfig,
modifiedDate?: string | Date,
overrides?: Partial<SitemapRoute>
): SitemapRoute
// Example
const sitemapEntry = generateSitemapUrl('/about', siteConfig, '2024-01-15')sortSitemapUrls()
Sort sitemap URLs by priority (high to low) and lastModified (recent to old).
function sortSitemapUrls(urls: SitemapRoute[]): SitemapRoute[]validateSitemapUrl()
Validate a sitemap URL entry.
function validateSitemapUrl(route: SitemapRoute): booleansplitSitemapUrls()
Split sitemap into chunks (Google recommends max 50,000 URLs).
function splitSitemapUrls<T>(urls: T[], chunkSize: number = 50000): T[][]
// Example
const chunks = splitSitemapUrls(urls, 50000)Video Utilities
VideoData Interface
interface VideoData {
type: 'youtube' | 'youtube-short' | 'tiktok' | 'instagram-reel' | 'facebook-reel' | 'vimeo' | 'mp4' | 'other'
embedUrl?: string
contentUrl?: string
videoId?: string
thumbnailUrl?: string
duration?: string // ISO 8601 format (e.g., "PT3M10S")
uploadDate?: string
name?: string
description?: string
isShortForm?: boolean // True for short-form content (under 60s)
}extractVideosFromContent()
Extract all videos from HTML content. Supports:
- YouTube (regular, shorts, youtu.be)
- TikTok
- Instagram Reels
- Facebook Reels
- Vimeo
- Self-hosted MP4
function extractVideosFromContent(htmlContent: string): VideoData[]
// Example
const videos = extractVideosFromContent(articleContent)
// Result: [
// {
// type: 'youtube',
// videoId: 'dQw4w9WgXcQ',
// embedUrl: 'https://www.youtube.com/embed/dQw4w9WgXcQ',
// contentUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
// thumbnailUrl: 'https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg'
// }
// ]extractDurationFromText()
Extract video duration from article text. Looks for patterns like:
- "3 minutes 10 seconds"
- "3:10"
- "3m10s"
function extractDurationFromText(text: string): string | undefined
// Example
const duration = extractDurationFromText('Watch this 3:45 video')
// Result: "PT3M45S" (ISO 8601 format)generateVideoSchema()
Generate VideoObject structured data for a single video.
function generateVideoSchema(
video: VideoData,
article: {
title: string
excerpt: string
date: string
modified: string
slug: string
featuredImage: string
},
baseUrl: string,
siteName: string = 'Site'
): any
// Returns Schema.org VideoObjectgenerateVideoSchemas()
Main function: Detect videos and generate all VideoObject schemas.
function generateVideoSchemas(
htmlContent: string,
article: {
title: string
excerpt: string
content: string
date: string
modified: string
slug: string
featuredImage: string
},
baseUrl: string,
siteName: string = 'Site'
): any[]
// Example
const videoSchemas = generateVideoSchemas(
article.content,
article,
siteConfig.baseUrl,
siteConfig.siteName
)
// Add to page metadata
export async function generateMetadata() {
const videoSchemas = generateVideoSchemas(...)
return {
// ... other metadata
other: {
'application/ld+json': JSON.stringify(videoSchemas)
}
}
}hasVideos()
Check if content contains any videos.
function hasVideos(htmlContent: string): boolean
// Example
if (hasVideos(article.content)) {
const schemas = generateVideoSchemas(...)
}normalizeDateWithTimezone()
Normalize date to ISO 8601 format with timezone.
function normalizeDateWithTimezone(dateString: string): stringUsage Examples
Example 1: Article Page with Full SEO
// app/[locale]/news/[slug]/page.tsx
import {
generatePageMetadata,
getArticleCanonicalUrl,
getLanguageAlternates,
generateVideoSchemas
} from '@next-wordpress/utils'
import { siteConfig } from '@/lib/site.config'
export async function generateMetadata({ params }) {
const article = await fetchArticle(params.slug)
// Generate video schemas if article contains videos
const videoSchemas = generateVideoSchemas(
article.content,
article,
siteConfig.baseUrl,
siteConfig.siteName
)
return generatePageMetadata(
{
title: article.title,
description: article.excerpt,
type: 'article',
image: article.featuredImage,
publishedTime: article.date,
modifiedTime: article.modified,
section: article.category,
tags: article.tags,
},
getArticleUrl(params.slug, params.locale, 'article', siteConfig),
siteConfig
)
}Example 2: Dynamic Sitemap
// app/sitemap.ts
import { MetadataRoute } from 'next'
import {
generateSitemapUrl,
sortSitemapUrls,
splitSitemapUrls
} from '@next-wordpress/utils'
import { siteConfig } from '@/lib/site.config'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const articles = await fetchAllArticles()
const articleUrls = articles.map(article =>
generateSitemapUrl(
`/news/${article.slug}`,
siteConfig,
article.modified
)
)
const staticUrls = [
generateSitemapUrl('/', siteConfig),
generateSitemapUrl('/about', siteConfig),
generateSitemapUrl('/contact', siteConfig),
]
const allUrls = [...staticUrls, ...articleUrls]
const sortedUrls = sortSitemapUrls(allUrls)
return sortedUrls
}Example 3: Article Card with SEO Slug
// components/article-card.tsx
import { generateSeoSlug, truncateText, stripHtml } from '@next-wordpress/utils/text'
import { getArticleUrl } from '@next-wordpress/utils/url'
import { siteConfig } from '@/lib/site.config'
export function ArticleCard({ article, locale }) {
const slug = generateSeoSlug(article.title, article.id)
const url = getArticleUrl(slug, locale, 'article', siteConfig)
const excerpt = truncateText(stripHtml(article.excerpt), 150)
return (
<a href={url}>
<h3>{article.title}</h3>
<p>{excerpt}</p>
</a>
)
}Example 4: Multi-Locale Breadcrumbs
// components/breadcrumb.tsx
import { buildLocalizedPath } from '@next-wordpress/utils/url'
import { siteConfig } from '@/lib/site.config'
export function Breadcrumb({ items, locale }) {
return (
<nav>
{items.map((item, index) => {
const path = buildLocalizedPath(item.href, locale, siteConfig)
return (
<a key={index} href={path}>
{item.label}
</a>
)
})}
</nav>
)
}TypeScript Support
All functions and types are fully typed. Your IDE will provide autocomplete and type checking:
import { SiteConfig, MetadataTemplate } from '@next-wordpress/utils'
// Type-safe configuration
const config: SiteConfig = {
baseUrl: 'https://yourdomain.com',
// ... TypeScript will validate all properties
}
// Type-safe template
const template: MetadataTemplate = {
title: 'Page Title',
description: 'Page description',
type: 'article', // Only accepts: 'website' | 'article' | 'product'
}License
MIT
Contributing
Contributions are welcome! Please ensure all tests pass and add tests for new features.
Support
For issues and feature requests, please use the GitHub issue tracker.
