@lacspace/seo
v1.6.1
Published
Typed metadata + JSON-LD for modern web apps — schema.org builders (Organization, Article, Product, FAQ, Breadcrumb), a Next.js App Router Metadata helper, the defineSite() SEO Autopilot engine, and an on-page SEO auditor with a CI sitemap crawler that fa
Maintainers
Readme
@lacspace/seo
Typed metadata + JSON-LD — stop copy-pasting fragile structured data.
Build valid schema.org JSON-LD and Next.js App Router
Metadataobjects with typed one-liners. Autocomplete instead of guesswork; no malformed rich-results markup.
- 🏷️
seoMetadata()→ a NextMetadataobject (title, description, canonical, OG, Twitter) - 🧩 16 JSON-LD builders:
organization·website·article·product·breadcrumb·faqPage·softwareApp·localBusiness·event·person·review·videoObject·howTo·jobPosting·course·recipe - 🌐
hreflang()for multilingualalternates - 🛡️
jsonLdScript()renders a safe<script>(escapes</script>) - ⚡ Zero dependencies · 🌍 isomorphic (any framework) · 📦 ESM + CJS · fully typed
Install
npm install @lacspace/seo # or pnpm add / yarn add / bun addNext.js metadata in one line
// app/pricing/page.tsx
import { seoMetadata } from "@lacspace/seo";
export const metadata = seoMetadata({
title: "Pricing — Lacspace",
description: "Simple, transparent plans.",
canonical: "/pricing",
image: "https://lacspace.com/og/pricing.png",
baseUrl: "https://lacspace.com",
});JSON-LD, typed
import { article, breadcrumb, faqPage, jsonLdScript } from "@lacspace/seo";
const schema = article({
headline: "Launching StockKit",
author: { name: "Lumi AI", url: "https://lacspace.com" },
datePublished: "2026-08-22",
image: "https://lacspace.com/og/stockkit.png",
publisher: { name: "Lacspace", url: "https://lacspace.com", logo: "https://lacspace.com/logo.png" },
});
// In a Server Component:
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} />
// Or get the whole tag as a string (safely escaped):
jsonLdScript(faqPage([{ question: "Is it free?", answer: "Yes — MIT licensed." }]));breadcrumb([
{ name: "Home", url: "https://lacspace.com" },
{ name: "Blog", url: "https://lacspace.com/blog" },
]);Builders
| Builder | schema.org type |
| --- | --- |
| organization(o) | Organization |
| website(o) | WebSite (+ SearchAction) |
| article(o) | Article |
| product(o) | Product (+ Offer, AggregateRating) |
| breadcrumb(items) | BreadcrumbList |
| faqPage(items) | FAQPage |
| softwareApp(o) | SoftwareApplication |
| localBusiness event person review | LocalBusiness / Event / Person / Review |
| videoObject howTo jobPosting course recipe | VideoObject / HowTo / JobPosting / Course / Recipe |
| seoMetadata(input) | Next.js Metadata |
| hreflang(map) | alternates.languages for Next |
The Lacspace SEO Kit
| Package | For |
| --- | --- |
| @lacspace/seo | Metadata & JSON-LD (this package) |
| @lacspace/sitemap | sitemap.xml |
| @lacspace/robots | robots.txt |
| @lacspace/llms-txt | llms.txt / llms-full.txt |
| @lacspace/site-verify | Search-engine verification |
| @lacspace/rss | RSS / Atom / JSON feeds |
| @lacspace/slugify | SEO URL slugs |
New in 1.2 — @graph, breadcrumbs-from-path, richer OG & linting
import { graph, organization, website, breadcrumbFromPath, seoMetadata, lintSeo, blogPosting, jsonLdScript } from "@lacspace/seo";
// Compose many nodes into ONE @graph (shared @context, no duplication)
const ld = graph(organization({ name: "Lacspace", url: "https://lacspace.com" }), website({ name: "Lacspace", url: "https://lacspace.com" }));
// Breadcrumbs straight from the URL path — no manual wiring
breadcrumbFromPath("/blog/my-post", { baseUrl: "https://x.com" });
// Richer social cards + i18n in one call
export const metadata = seoMetadata({
title, description, image, imageAlt, imageWidth: 1200,
type: "article", article: { publishedTime, authors: ["Lumi"] },
twitterSite: "@lacspace", languages: { en: "/en", ne: "/ne" },
});
// Catch SEO mistakes before deploy
lintSeo({ title, description, canonical, image }).warnings; // ["description is 210 chars (>160…)"]Also: blogPosting, newsArticle, webPage builders.
New in 1.5 — SEO auditor (generate and grade)
Grade any live page's on-page SEO from the terminal — or auditHtml() in code/CI:
npx @lacspace/seo audit https://example.com
# Score 88/100 Grade B · title/description/canonical/OG/JSON-LD/alt/indexable …
npx @lacspace/seo audit https://example.com --json # machine output for CI (exits non-zero on any fail)import { auditHtml } from "@lacspace/seo";
const report = auditHtml(await (await fetch(url)).text(), { url });
report.score; // 0–100
report.grade; // "A" … "F"
report.checks; // [{ id, label, status: "pass"|"warn"|"fail", detail }]Checks title, meta description, canonical, single H1, Open Graph, Twitter card, viewport, lang, charset, JSON-LD validity, image alts and indexability — each with a human explanation.
New in 1.4 — scaffolder + more page presets
Skip the setup entirely — scaffold the whole SEO layer into a Next.js app:
npm create lacspace-seo@latest
# drops in lib/site.ts + robots.txt, sitemap.xml, feed.xml, llms.txt & a dynamic OG routeAnd three more defineSite() presets, each returning { metadata, jsonLd }:
site.softwareApp({ title: "My App", path: "/app", price: 0, operatingSystem: "Web" });
site.event({ title: "Launch", path: "/events/launch", startDate: "2026-09-01", online: true });
site.localBusiness({ title: "Acme Cafe", path: "/cafe", telephone: "+1-555-0100", rating: { value: 4.8, count: 30 } });New in 1.3 — SEO Autopilot (configure once, auto-fill everything)
Set your brand once with defineSite(), then every page's metadata and JSON-LD is a one-liner — canonical URL, title template, Open Graph, Twitter card, auto description and auto OG image all filled in for you.
import { defineSite, jsonLdScript } from "@lacspace/seo";
export const site = defineSite({
name: "Acme",
url: "https://acme.com",
logo: "/logo.png",
twitter: "acmehq",
ogImage: "/og", // dynamic social cards → /og?title=<page> (zero design work)
searchUrl: "https://acme.com/search?q={search_term_string}",
});
// app/layout.tsx — sitewide Organization + WebSite, declared once
// <>{/* */}<div dangerouslySetInnerHTML={{ __html: jsonLdScript(site.rootJsonLd()) }} /></>
// app/pricing/page.tsx
export const metadata = site.meta({ title: "Pricing", path: "/pricing" });
// → "Pricing · Acme" + canonical + OG + Twitter + og:image?title=Pricing
// app/blog/[slug]/page.tsx — metadata + BlogPosting + BreadcrumbList in ONE call
const { metadata, jsonLd } = site.article({
title: post.title,
path: `/blog/${post.slug}`,
datePublished: post.date,
author: "Lumi AI",
content: post.body, // ← description auto-derived, no copywriting
});Also site.product(...), site.faq(...), site.page(...) — each returns { metadata, jsonLd }. Plus content auto-derivation helpers you can use anywhere: excerpt(), metaDescription(), readingTime(), stripMarkdown() and ogImageUrl().
Licensing
This package is free under the Lacspace Free Licence — MIT-equivalent freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
Not every Lacspace package is free. We also offer Commercial (paid), Client-specific, and Private (proprietary) packages under separate terms. See the full Lacspace Licence Centre.
Part of the Lacspace ecosystem — 35 zero-dependency, isomorphic TypeScript packages.
