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

@tavojs/structured-data

v1.0.1

Published

Metadata-driven Schema.org JSON-LD generation for Tavo.js apps.

Readme

@tavojs/structured-data

Metadata-driven Schema.org JSON-LD for Tavo.js applications.

Configure site identity and product facts once, connect an optional route metadata resolver, and render the schemas selected for the current route. The package includes typed builders for WebSite, Organization, SoftwareApplication, and BreadcrumbList, plus an escape hatch for other Schema.org types.

Install

npm install @tavojs/structured-data

Configure A Site Once

Create an application-owned module such as src/seo/structured-data.ts:

import { createStructuredDataSite } from "@tavojs/structured-data";

export const structuredData = createStructuredDataSite({
  siteUrl: "https://example.com",
  // Use this explicit fallback when the helper is not installed as a plugin.
  urlPolicy: { trailingSlash: "always" },
  website: {
    name: "Example",
    alternateName: ["example.com"]
  },
  organization: {
    name: "Example Project",
    logo: "/images/logo.png",
    sameAs: ["https://github.com/example"]
  },
  applications: [{
    path: "/",
    name: "Example Framework",
    description: "A framework for complete web applications.",
    applicationCategory: "DeveloperApplication",
    operatingSystem: "Any",
    offers: { price: 0 }
  }],
  resolve({ pathname }) {
    return pathname === "/docs/button"
      ? {
          breadcrumbs: [
            { name: "Docs", url: "/docs" },
            { name: "Button" }
          ]
        }
      : undefined;
  }
});

Call the builder from a root layout head function:

import { Seo } from "@tavojs/core";
import type { PageLoadContext } from "@tavojs/core/router";
import { structuredData } from "src/seo/structured-data";

export function head(context: PageLoadContext) {
  const canonical = new URL(context.pathname, "https://example.com").href;
  return (
    <>
      <Seo canonical={canonical} openGraph={{ url: canonical }} />
      {structuredData.head(context)}
    </>
  );
}

The homepage emits WebSite, Organization, and the configured SoftwareApplication in one @graph. The documentation route emits only its BreadcrumbList. Routes without metadata emit no JSON-LD.

URL Policy

Site-relative page URLs follow routing.trailingSlash when the site helper is bound through the plugin context:

import {
  createStructuredDataPlugin,
  createStructuredDataSite
} from "@tavojs/structured-data";

const structuredData = createStructuredDataSite({
  siteUrl: "https://example.com",
  website: { name: "Example" },
  organization: { name: "Example" }
});

export default {
  routing: { trailingSlash: "always" },
  plugins: [createStructuredDataPlugin({ site: structuredData })]
};

For standalone use, pass urlPolicy: { trailingSlash: "always" | "never" | "preserve" } to createStructuredDataSite(), createBreadcrumbList(), or createSoftwareApplication(). The policy applies to breadcrumb items, application pages, and offer pages. It retains queries and fragments, leaves explicit absolute URLs unchanged, preserves entity IDs such as /#organization, and never appends a slash to file resources.

Loader Data

A page head receives the resolved value from its page load() function. Pass only application metadata; the package creates the Schema.org shape:

import type { PageLoadContext } from "@tavojs/core/router";
import { structuredData } from "src/seo/structured-data";

type ProductPageData = {
  breadcrumbs: Array<{ name: string; url?: string }>;
};

export function head(
  context: PageLoadContext & { data?: ProductPageData; error?: unknown }
) {
  return structuredData.head(context, {
    breadcrumbs: context.data?.breadcrumbs
  });
}

A layout head receives its own layout loader data, not a descendant page's loader data. Put data-dependent schema in the page head or in statically known route metadata.

Direct Builders

Use the lower-level component and builders for exceptional routes:

import {
  StructuredData,
  createBreadcrumbList,
  createSoftwareApplication
} from "@tavojs/structured-data";

export const head = (
  <StructuredData
    id="product-schema"
    data={[
      createSoftwareApplication({
        siteUrl: "https://example.com",
        url: "/product",
        name: "Example Framework",
        applicationCategory: "DeveloperApplication",
        operatingSystem: "Any",
        offers: { price: 0 }
      }),
      createBreadcrumbList({
        siteUrl: "https://example.com",
        items: [
          { name: "Products", url: "/products" },
          { name: "Example Framework" }
        ]
      })
    ]}
  />
);

Multiple nodes become one @graph. Inline JSON is escaped through Tavo.js's script renderer, including closing script sequences.

Global Plugin Mode

Use global mode only for a schema deliberately repeated on every SSR or prerendered page:

import { defineConfig } from "@tavojs/core/config";
import {
  createStructuredDataPlugin,
  defineStructuredData
} from "@tavojs/structured-data";

const globalSchema = createStructuredDataPlugin({
  id: "global-schema",
  data: defineStructuredData({
    "@type": "Organization",
    name: "Example",
    url: "https://example.com"
  })
});

export default defineConfig({ plugins: [globalSchema] });

For search site names and organization identity, prefer homepage metadata. A root-layout head(context) can make that choice from the current pathname.

More Documentation

Project Policies