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

@paragraphcms/seo

v0.2.2

Published

SEO document generators for Paragraph CMS sites.

Readme

@paragraphcms/seo

SEO document generators for Paragraph CMS sites.

@paragraphcms/seo builds robots.txt, sitemap.xml, rss.xml, and llms.txt from a single Client instance from @paragraphcms/client plus your route definitions.

Install

npm install @paragraphcms/client @paragraphcms/seo
# or
pnpm add @paragraphcms/client @paragraphcms/seo
# or
yarn add @paragraphcms/client @paragraphcms/seo
# or
bun add @paragraphcms/client @paragraphcms/seo

Quick Start

import { Client } from "@paragraphcms/client";
import {
  SEO,
  localizedContentRoute,
  localizedRoute,
} from "@paragraphcms/seo";

const client = new Client({
  apiKey: process.env.PARAGRAPH_API_KEY!,
});

const seo = new SEO({
  client,
  site: {
    url: "https://example.com",
    name: "My site",
    description: "Latest posts from my site.",
    defaultLocale: "en",
  },
  routes: {
    home: localizedRoute(),
    blog: localizedContentRoute("blog", {
      params: {
        collection: "blog",
      },
    }),
    features: localizedContentRoute("features", {
      params: {
        collection: "features",
      },
    }),
  },
});

const robots = await seo.robotsTxt();
const sitemap = await seo.sitemapXml();
const blogRss = await seo.rssXml({
  locale: "en",
  route: "blog",
});
const llms = await seo.llmsTxt();

Default Locale

If you do not pass site.defaultLocale, the library resolves it with client.locales.getDefaultLocale().

site.defaultLocale always wins when both are available.

rssXml() and llmsTxt() use the resolved default locale when locale is omitted.

Route Params

Each content route lives directly under routes, and you can attach filtering params right there:

import {
  localizedContentRoute,
  localizedRoute,
} from "@paragraphcms/seo";

routes: {
  home: localizedRoute(),
  blog: localizedContentRoute("blog", {
    params: {
      collection: "Blog",
    },
  }),
  features: localizedContentRoute("features", {
    params: {
      collection: "Features",
    },
  }),
}

params is forwarded to client.pages.list() for that route, with language and requiredSlug filled in automatically by the library.

published defaults to true for every content route. If you need unpublished entries for a specific route, override it with published: false.

The Paragraph client also supports status filters in pages.list(), so you can pass statusId or statusType in params when needed.

If you prefer plain objects, content routes also support basePath directly:

routes: {
  home: localizedRoute(),
  blog: {
    basePath: "blog",
    params: {
      collection: "Blog",
    },
  },
}

basePath: "blog" expands to /blog for the default locale and /:locale/blog for other locales. Post URLs are generated automatically by appending the page slug.

Localized Route Helpers

localizedRoute() returns a function, so this:

home: localizedRoute(),

is equivalent to writing:

home: ({ locale, defaultLocale }) =>
  locale === defaultLocale ? "/" : `/${locale}`,

Pass a path when you want a localized non-root route:

const docsRoute = localizedRoute("docs");

RSS

If you configure more than one content route, pass route to rssXml():

const rss = await seo.rssXml({
  locale: "en",
  route: "features",
});

Artifact Paths

You can override the public paths used inside generated documents:

const seo = new SEO({
  client,
  site,
  routes,
  artifacts: {
    sitemapPath: "/sitemap.xml",
    robotsPath: "/robots.txt",
    llmsPath: "/llms.txt",
    rssPath: ({ locale, route, collection, defaultLocale }) =>
      locale === defaultLocale
        ? `/${route}-${collection?.toLowerCase()}.xml`
        : `/${locale}/${route}-${collection?.toLowerCase()}.xml`,
  },
});

API

new SEO(options)

Creates an instance with four async methods:

  • robotsTxt()
  • sitemapXml()
  • rssXml({ locale?, route? })
  • llmsTxt({ locale? })

License

MIT