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.1.4

Published

Modular SEO helpers, schema builders, Svelte components, and Drizzle storage for SvelteKit.

Readme

@alufie/seo

SEO building blocks for SvelteKit.

@alufie/seo gives you:

  • site and route SEO config
  • final SEO resolution
  • <head> rendering
  • JSON-LD/schema helpers
  • optional Drizzle storage
  • optional editor and form helpers

The package is subpath-only.

Use explicit imports like:

  • @alufie/seo/defaults
  • @alufie/seo/resolve
  • @alufie/seo/schemas
  • @alufie/seo/drizzle
  • @alufie/seo/editor
  • @alufie/seo/validate
  • @alufie/seo/svelte
  • @alufie/seo/types

Index

  1. Install
  2. Import Style
  3. Package Hierarchy
  4. Quick Start
  5. Files To Create In Your App
  6. Step 1: Create Site Defaults
  7. Step 2: Return SEO From Routes
  8. Step 3: Render SEO In Root Layout
  9. Optional: Store Route SEO In DB
  10. Optional: Use The Editor
  11. Functions And Components By Module
  12. Common App File Layout
  13. Publish And Security Notes
  14. Potential Todo

Install

pnpm add @alufie/seo

or:

npm install @alufie/seo

Import Style

Use explicit subpaths.

Example:

import { createSeoConfig, defineSeoSite } from '@alufie/seo/defaults';
import { resolveSeoPage } from '@alufie/seo/resolve';
import { buildBreadcrumbSchema, buildFaqSchema } from '@alufie/seo/schemas';
import { SeoHead } from '@alufie/seo/svelte';
import type { SeoInput } from '@alufie/seo/types';

Why:

  • avoids accidental client bundling of server-only code
  • keeps each import’s intent obvious
  • matches the package’s module boundaries

Package Hierarchy

Think of the package like this:

Subpath: @alufie/seo/defaults

Use this if you only want config and title/meta helpers.

Subpath: @alufie/seo/resolve

Use this if you only want resolution and final tag or JSON-LD output.

Subpath: @alufie/seo/schemas

Use this if you only want schema builders.

Subpath: @alufie/seo/drizzle

Use this if you only want DB table and store helpers.

Subpath: @alufie/seo/editor

Use this if you only want form parsing, editor loading, and save helpers.

Subpath: @alufie/seo/validate

Use this if you only want normalization and validation.

Subpath: @alufie/seo/svelte

Use this if you only want Svelte components.

Subpath: @alufie/seo/types

Use this for public shared types.

Quick Start

The normal setup is:

  1. create one SEO config file
  2. return seo from routes when needed
  3. resolve and render in root layout

That is enough for a working setup.

Files To Create In Your App

Minimum setup:

  • src/lib/seo.ts site-wide SEO defaults
  • src/routes/+layout.svelte final SEO resolution and rendering

Optional setup:

  • src/routes/**/+page.ts route-level SEO
  • src/routes/**/+layout.ts section-level SEO
  • src/lib/server/seo-store.ts DB-backed route SEO
  • src/routes/admin/... editor pages using SeoEditor or form helpers

Step 1: Create Site Defaults

Create:

src/lib/seo.ts

import { createSeoConfig, defineSeoSite } from '@alufie/seo/defaults';

export const seoConfig = createSeoConfig({
	site: defineSeoSite({
		siteName: 'Example Site',
		siteUrl: 'https://example.com',
		description: 'Clear default description for the whole site.',
		locale: 'en_US',
		image: 'https://example.com/og-default.jpg',
		titleJoin: ' | '
	})
});

This file gives you:

  • fallback site title
  • fallback description
  • canonical base URL
  • default OG image
  • locale

Step 2: Return SEO From Routes

You can return SEO from:

  • +page.ts
  • +layout.ts
  • +page.server.ts
  • +layout.server.ts

Example:

src/routes/guides/[slug]/+page.ts

import { defineSeoPage } from '@alufie/seo/defaults';
import { buildBreadcrumbSchema, buildFaqSchema } from '@alufie/seo/schemas';
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ params, url }) => {
	const canonical = new URL(url.pathname, url.origin).toString();
	const title = `Guide: ${params.slug}`;

	return {
		seo: defineSeoPage({
			title,
			description: 'A practical guide page.',
			canonical,
			ogType: 'article',
			publishedAt: '2026-03-25T00:00:00.000Z',
			schemas: [
				buildBreadcrumbSchema({
					fallbackName: 'Guide',
					itemListElement: [
						{ name: 'Home', item: url.origin },
						{ name: 'Guides', item: `${url.origin}/guides` },
						{ name: params.slug, item: canonical, fallbackName: title }
					]
				}),
				buildFaqSchema({
					mainEntity: [
						{
							name: 'Who is this page for?',
							acceptedAnswer: {
								text: 'Anyone who needs a clear example.'
							}
						}
					]
				})
			]
		})
	};
};

Use +layout.ts the same way if you want section-wide defaults.

Schema validation and fallbacks

Schema builders default to safe mode because route data is often fed by a CMS. Safe mode trims text and URLs, omits empty optional fields, filters unusable FAQ/HowTo rows, and uses breadcrumb fallback labels instead of emitting empty names.

import { buildBreadcrumbSchema } from '@alufie/seo/schemas';
import { validateSchemas } from '@alufie/seo/validate';

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

const warnings = validateSchemas([breadcrumb]);

Use strict mode in tests or CI when missing required fields should fail loudly:

buildBreadcrumbSchema(
	{
		itemListElement: [{ name: page.name, item: canonical }]
	},
	{ mode: 'strict' }
);

buildTrailSchema() remains available for compatibility. Prefer buildBreadcrumbSchema() in new code because it matches Google’s breadcrumb structured data terminology.

Step 3: Render SEO In Root Layout

Create or update:

src/routes/+layout.svelte

<script lang="ts">
	import { page } from '$app/state';
	import { resolveSeoPage } from '@alufie/seo/resolve';
	import { SeoHead } from '@alufie/seo/svelte';
	import { seoConfig } from '$lib/seo';

	let { children } = $props();

	const seo = $derived(
		resolveSeoPage({
			site: seoConfig.site,
			routePath: page.url.pathname,
			pageUrl: page.url,
			file: page.data.seo
		})
	);
</script>

<SeoHead seo={seo} />

{@render children?.()}

Import map for this file:

  • from your app: seoConfig
  • from @alufie/seo/resolve: resolveSeoPage
  • from @alufie/seo/svelte: SeoHead
  • from SvelteKit: page

Optional: Store Route SEO In DB

Use this if you want route-level SEO records in a database.

Install drizzle-orm in your app before using the Drizzle helpers:

pnpm add drizzle-orm

Create src/lib/server/seo-store.ts

import { createSeoStore } from '@alufie/seo/drizzle';
import { db } from '$lib/server/db';

export const seoStore = createSeoStore(db);

createSeoStore(db) uses the SQLite schema by default. For PostgreSQL, pass dialect: 'pg':

export const seoStore = createSeoStore(db, {
	dialect: 'pg'
});

Migrations are app-owned. The package provides Drizzle table builders and store helpers; it does not generate or ship app migrations. Add a uniqueness constraint for (route_key, locale) in your app schema or migration because store lookups treat that pair as logically unique. Decide explicitly how your database should represent the default locale, especially when mixing locale = null with locale strings such as en or zh-TW.

Read a stored record in a route

import { defineSeoPage } from '@alufie/seo/defaults';
import { seoStore } from '$lib/server/seo-store';

const stored = await seoStore.findByRoute('/about');

return {
	seo: defineSeoPage({
		...stored?.input
	})
};

Merge DB SEO and route SEO

import { resolveSeoPage } from '@alufie/seo/resolve';

const seo = resolveSeoPage({
	site: seoConfig.site,
	db: stored?.input,
	file: page.data.seo,
	routePath: page.url.pathname,
	pageUrl: page.url
});

Use a custom table name

import { createSeoStore } from '@alufie/seo/drizzle';

export const seoStore = createSeoStore(db, {
	tableName: 'route_seo',
	routeKeyColumn: 'path_key'
});

Optional: Use The Editor

Use this if you want a simple admin or CMS flow.

Render SeoEditor

<script lang="ts">
	import { SeoEditor } from '@alufie/seo/svelte';

	const value = {
		routeKey: '/about',
		enabled: true,
		input: {
			title: 'About',
			description: 'About page'
		}
	};
</script>

<SeoEditor value={value} />

Parse posted form data

import { parseSeoFormData } from '@alufie/seo/editor';

const formData = await request.formData();
const result = parseSeoFormData(formData);

Save directly to the built-in store

import { saveSeoForm } from '@alufie/seo/editor';

const formData = await request.formData();
const saved = await saveSeoForm(db, seoTable, formData, {});

Load editor data

import { loadSeoEditor } from '@alufie/seo/editor';

const editorValue = await loadSeoEditor(seoStore, '/about');

Functions And Components By Module

@alufie/seo/defaults

Smaller config-only surface:

  • createSeoConfig()
  • defineSeoSite()
  • defineSeoPage()
  • buildSeoTitle()
  • buildSeoMeta()

@alufie/seo/resolve

Resolution-only surface:

  • resolveSeoPage()
  • mergeSeoInput()
  • buildSeoJsonLd()
  • buildSeoTags()

@alufie/seo/schemas

Schema-builder-only surface:

  • all build*Schema() helpers
  • buildBreadcrumbSchema() is the preferred breadcrumb helper
  • buildTrailSchema() is kept as a backwards-compatible alias

@alufie/seo/drizzle

DB-only surface:

  • createSeoTable()
  • createSqliteSeoTable()
  • createPgSeoTable()
  • createSeoStore()
  • defineSeoStore()
  • createSeoQueries()
  • findSeoByRoute()
  • upsertSeoByRoute()
  • listSeoRecords()
  • deleteSeoByRoute()
  • getSeoRecord()
  • saveSeoRecord()

@alufie/seo/editor

Editor and form-only surface:

  • serializeSeoInput()
  • parseSeoFormData()
  • loadSeoEditor()
  • saveSeoForm()
  • saveSeoEditor()

@alufie/seo/validate

Validation-only surface:

  • normalizeSeoInput()
  • validateSeoInput()
  • validateSchemas()
  • createSeoEditorValue()

@alufie/seo/svelte

Svelte-components-only surface:

  • SeoHead
  • SeoJsonLd
  • SeoForm
  • SeoEditor
  • ArticleSchemaFields
  • FaqSchemaFields
  • MedicalSchemaFields
  • ProductSchemaFields
  • TrailSchemaFields

@alufie/seo/types

Shared public types:

  • SeoInput
  • SeoPage
  • SeoSite
  • SeoSiteProfile
  • SeoRecord
  • SeoResult
  • SeoConfig
  • SeoSchema
  • SeoEditorValue

Common App File Layout

src/
  lib/
    seo.ts
    server/
      seo-store.ts
  routes/
    +layout.svelte
    about/
      +page.ts
    admin/
      seo/
        +page.server.ts
        +page.svelte

Publish And Security Notes

  • package name: @alufie/seo
  • publish access: public
  • security policy: see SECURITY.md
  • package contents are limited by the files list in package.json

Potential Todo

  • add an optional convenience facade like createSeoKit() that wraps common flows without replacing the modular API