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

@jdevalk/emdash-plugin-seo

v0.4.2

Published

SEO plugin for EmDash CMS — meta tags, Open Graph, canonical URLs, robots directives, and JSON-LD schema markup

Downloads

746

Readme

EmDash SEO Plugin

CI Lint npm License: MIT TypeScript EmDash Plugin

An SEO plugin for EmDash CMS that generates meta tags, Open Graph, Twitter Cards, canonical URLs, robots directives, and JSON-LD schema markup via the page:metadata hook.

Features

  • Meta descriptions with configurable fallback chain
  • Meta robots with max-snippet, max-image-preview, and max-video-preview directives; noindex for search/utility pages; omitted on 404
  • Canonical URLs — absolute, normalized, with trailing slash and pagination support
  • Open Graphog:title without site name suffix, og:type: article for content pages, full set of OG tags
  • Twitter Cardssummary_large_image when image present, site handle from settings
  • JSON-LD schema graph with linked nodes:
    • Person or Organization (configurable)
    • WebSite with SearchAction
    • WebPage (CollectionPage for archives)
    • Article with author Person (for content pages)
    • BreadcrumbList with a back-reference from WebPage
  • Breadcrumbs — derived from the URL path by default, with segment label overrides (/blog/ → "Blog") and per-pageType rule overrides both editable in the admin UI. @id scheme matches joost.blog via @jdevalk/seo-graph-core
  • hreflang alternates — for multilingual EmDash sites (Astro i18n + translation_group), one <link rel="alternate" hreflang="…"> per published sibling plus an automatic x-default, with BCP 47 tag normalization (fr-cafr-CA). Zero cost on single-locale sites
  • Admin settings UI — auto-generated from settingsSchema for configuring Person/Organization identity, social profiles, title separator, and default description

Installation

Copy the src/ directory into your EmDash theme's plugins/seo/ directory, or install from this repo:

# In your emdash theme directory
cp -r path/to/emdash-plugin-seo/src plugins/seo/src
cp path/to/emdash-plugin-seo/package.json plugins/seo/package.json

Usage

Register the plugin in your astro.config.mjs:

import { seoPlugin } from "./plugins/seo/src/index.ts";

export default defineConfig({
  integrations: [
    emdash({
      plugins: [seoPlugin()],
    }),
  ],
});

Then configure your site identity and social profiles in the EmDash admin under Plugins > SEO > Settings.

Settings

| Setting | Description | |---------|-------------| | Site represents | Person or Organization | | Title separator | Character between page title and site name (em dash, pipe, hyphen, dot) | | Default meta description | Fallback for pages without their own | | Person name / bio / image / job title / URL | Person schema fields | | Organization name / logo URL | Organization schema fields | | Social URLs | Twitter/X, Facebook, LinkedIn, Instagram, YouTube, GitHub, Bluesky, Mastodon, Wikipedia | | Breadcrumb segment labels | segment → display label overrides (e.g. blog → Blog) | | Breadcrumb page type rules | Per-pageType ordered crumb lists, JSON-edited, for themes that need full control over trail shape |

Multilingual sites (hreflang)

When your site has more than one locale configured in Astro's i18n block and content entries are linked via translation_group, the plugin automatically emits hreflang annotations for each content page. No configuration required — it activates as soon as isI18nEnabled() returns true.

// astro.config.mjs
export default defineConfig({
  i18n: {
    defaultLocale: "en",
    locales: ["en", "fr", "nl"],
    routing: { prefixDefaultLocale: false },
  },
  integrations: [emdash({ plugins: [seoPlugin()] })],
});

A 3-locale post at /hello/, with published French (/fr/bonjour/) and Dutch (/nl/hallo/) translations in the same translation_group, renders:

<link rel="alternate" hreflang="en"        href="https://example.com/hello/">
<link rel="alternate" hreflang="fr"        href="https://example.com/fr/bonjour/">
<link rel="alternate" hreflang="nl"        href="https://example.com/nl/hallo/">
<link rel="alternate" hreflang="x-default" href="https://example.com/hello/">

Only published siblings are included. Drafts, scheduled entries, and siblings whose locale is no longer in your Astro config are dropped. If the page has fewer than two published locales, no hreflang tags are emitted (a single-locale page has no meaningful alternates).

Region-specific locales (fr-CA vs fr-FR)

If you need region-specific hreflang, use the BCP 47 code as the locale path directly:

i18n: {
  defaultLocale: "en",
  locales: ["en", "fr-ca", "fr-fr"],
}

URLs become /fr-ca/… and /fr-fr/…, and the emitted hreflang attributes are normalized to conventional casing (fr-CA, fr-FR). EmDash core currently drops Astro's object-form { path, codes } shape at the integration boundary, so the code-as-path workaround is the supported path for region tags in this plugin version.

Requirements

Requires EmDash with support for running page:metadata hooks on public pages for anonymous visitors. See emdash-cms/emdash#166 and PR #169.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

If you find a security vulnerability, please follow the security policy instead of opening a public issue.

License

MIT — see LICENSE for details.