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

@alufie/seo

v0.2.2

Published

Headless SEO resolution, robots, validation, and JSON-LD helpers with optional Svelte head rendering.

Downloads

512

Readme

@alufie/seo

Headless SEO resolution, robots controls, validation, and JSON-LD helpers with an optional Svelte head-rendering adapter.

The package owns no database, CMS, editor, routes, hostname, or application-specific schema. Applications own their metadata sources and pass plain objects into deterministic functions.

Install

pnpm add @alufie/seo

Install Svelte only when using @alufie/seo/svelte:

pnpm add svelte

Package surface

The package is subpath-only:

  • @alufie/seo/defaults — typed site/page definitions and title composition
  • @alufie/seo/resolve — ordered metadata composition, canonical resolution, tags, JSON-LD
  • @alufie/seo/robots — robots meta, robots.txt, and purpose-specific crawler policies
  • @alufie/seo/schemas — safe and strict schema.org builders
  • @alufie/seo/validate — input normalization and validation
  • @alufie/seo/types — shared public types
  • @alufie/seo/svelte — optional SeoHead and SeoJsonLd renderers

There is no root @alufie/seo barrel. Explicit subpaths keep runtime intent and dependency boundaries visible.

Quick start

import { defineSeoPage, defineSeoSite } from '@alufie/seo/defaults';
import { resolveSeoPage } from '@alufie/seo/resolve';
import { buildArticleSchema, buildOrgSchema, buildSiteSchema } from '@alufie/seo/schemas';

const site = defineSeoSite({
	siteName: 'Example',
	siteUrl: 'https://example.com',
	description: 'Example site',
	schemas: [
		buildOrgSchema({
			name: 'Example',
			url: 'https://example.com'
		}),
		buildSiteSchema({
			name: 'Example',
			url: 'https://example.com'
		})
	]
});

const routeSeo = defineSeoPage({
	title: 'Guide',
	description: 'A practical guide.',
	schemas: [
		buildArticleSchema({
			headline: 'Guide',
			description: 'A practical guide.',
			url: 'https://example.com/guides/example'
		})
	]
});

const seo = resolveSeoPage({
	site,
	layers: [routeSeo],
	routePath: '/guides/example',
	pageUrl: 'https://www.example.com/guides/example?preview=1',
	canonicalMode: 'site-origin'
});

The result contains the final title, canonical, robots value, Open Graph and Twitter inputs, alternates, and composed schemas.

Ordered metadata layers

layers is an ordered list of plain SeoInput objects. Later values override earlier scalar values. Schema arrays compose instead of replacing one another; a later schema with the same @id replaces the earlier entity while unrelated entities remain.

Use image: null in a later layer to clear an inherited site or section image explicitly.

const seo = resolveSeoPage({
	site,
	layers: [sectionDefaults, generatedMetadata, routeOverrides],
	pageUrl: url,
	canonicalMode: 'site-origin'
});

The package does not assign meaning to a layer. It can come from code, content, a CMS, a database, or any other application-owned source.

Canonical policies

resolveSeoPage() supports:

  • strip-query — default; removes query and fragment from the selected URL
  • full-url — keeps query and fragment and resolves relative values against a valid siteUrl
  • path-only — returns the path, query, and fragment without an origin
  • site-origin — forces the pathname onto the configured HTTP(S) site.siteUrl origin

site-origin accepts same-origin absolute canonicals and relative canonicals. Foreign or malformed explicit values are ignored. If siteUrl is missing or invalid, resolution fails closed to a relative current-page or route path rather than returning an unverified origin.

full-url emits only an absolute HTTP(S) canonical. When a selected value is relative, siteUrl must provide a valid origin; otherwise the resolver omits the canonical.

This policy is generic: applications choose the canonical origin through siteUrl; the package contains no consumer-specific hostname logic.

JSON-LD and schemas

buildSeoJsonLd():

  • emits an empty string when there are no schemas
  • removes structurally duplicate schemas regardless of object key order
  • preserves composed entities
  • serializes one entity as an object and multiple entities as an array
  • escapes < as \u003c for safe embedding in a script element

Schema builders default to safe mode. Safe mode trims strings and URLs, removes empty optional values, filters unusable collection entries, and supplies a fallback for missing required names.

const breadcrumb = buildBreadcrumbSchema({
	itemListElement: [
		{ name: ' Home ', item: ' https://example.com ' },
		{ name: page.title, item: canonical, fallbackName: 'Page' }
	]
});

Use strict mode in tests, CI, or authoring workflows where incomplete required data, malformed HTTP(S) URL fields, empty entity references, or non-finite numbers must fail. Supply id to any first-party builder when the entity needs a stable replacement key across metadata layers:

const article = buildArticleSchema(
	{
		headline: page.title,
		url: canonical
	},
	{ mode: 'strict', id: '#article' }
);

Product helpers cover identifiers, categories, aggregate ratings, offers, and variant relationships. Use buildProductGroupSchema() with productGroupID, variesBy, and hasVariant; variants can reference the group through isVariantOf or inProductGroupWithID.

buildCustomSchema() remains the explicit escape hatch for unsupported schema.org shapes. validateSchemaStructure() reports structural errors, including missing @type, missing or invalid @context, malformed nested URLs, non-finite numbers, and non-serializable values. validateSchemas() returns those errors plus search-feature eligibility warnings. FAQPage, HowTo, and SearchAction can remain valid schema.org markup even when a search engine no longer displays their former rich result.

Robots controls

Build page-level directives:

import { buildRobotsMeta } from '@alufie/seo/robots';

const robots = buildRobotsMeta({
	index: true,
	follow: true,
	maxImagePreview: 'large'
});

Build robots.txt:

import { buildAiCrawlerRules, buildRobotsTxt } from '@alufie/seo/robots';

const body = buildRobotsTxt({
	rules: [
		{ userAgent: '*', allow: '/', disallow: ['/admin/'] },
		...buildAiCrawlerRules({
			openAi: { search: 'allow', training: 'disallow', userFetch: 'allow' },
			anthropic: { search: 'allow', training: 'disallow', userFetch: 'allow' }
		})
	],
	sitemaps: 'https://example.com/sitemap.xml'
});

Search discovery, model training, and user-directed retrieval are separate controls. Configure each purpose explicitly; do not treat one crawler token as a universal AI switch. buildRobotsTxt() rejects CR/LF characters in user-agent, path, and sitemap values.

Validation

import { validateSeoInput } from '@alufie/seo/validate';

const result = validateSeoInput(routeSeo, {
	canonicalMode: 'site-origin',
	siteUrl: site.siteUrl
});

if (!result.valid) {
	console.error(result.errors);
}

console.warn(result.warnings);

Relative canonicals are accepted only when a valid site-origin policy supplies the resolution origin. Images and alternate URLs remain absolute HTTP(S) URLs. Schema structure participates in valid; search-feature eligibility remains available separately through warnings.

Optional Svelte rendering

<script lang="ts">
	import { SeoHead } from '@alufie/seo/svelte';
	import type { SeoResult } from '@alufie/seo/types';

	interface Props {
		seo: SeoResult;
	}

	let { seo }: Props = $props();
</script>

<SeoHead {seo} />

SeoHead renders the title, canonical, alternates, robots, description, Open Graph, Twitter, article metadata, and JSON-LD. It never emits the obsolete meta keywords tag.

Applications that use another framework can render SeoResult, buildSeoTags(), and buildSeoJsonLd() directly without installing Svelte.

Migrating from 0.1

Version 0.2 is intentionally breaking:

  • replace db and file resolver properties with ordered layers
  • replace createSeoConfig({ site }) with defineSeoSite(site)
  • remove @alufie/seo/drizzle, @alufie/seo/editor, editor components, and form presets
  • keep persistence and administrative metadata workflows in the consuming application
  • install Svelte only when importing @alufie/seo/svelte

Development

pnpm test
pnpm check
pnpm build

The publish gate runs all three commands. The package contents are restricted by the files list in package.json.

Primary references: