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

@nera-static/plugin-canonical-links

v2.2.1

Published

A plugin for Nera static site generator to create canonical links in to be used in the head.

Readme

@nera-static/plugin-canonical-links

Test npm version

A plugin for the Nera static site generator to generate canonical and alternate <link> tags for SEO in the document <head>. Helps search engines correctly index content across domains and languages.

✨ Features

  • Adds <link rel="canonical"> for SEO
  • Supports multilingual alternate links
  • Easy Pug template integration
  • Configurable origin and slug mapping

🚀 Installation

Install the plugin in the root of your Nera project:

npm install @nera-static/plugin-canonical-links

Nera will automatically detect the plugin and apply it during the build.

⚙️ Configuration

Create config/canonical-links.yaml:

app_origin: https://your-domain.com
page_identifier: slug
available_languages:
    - en
    - es
    - fr
  • app_origin: The canonical base URL. Used as a fallback — if origin is set in config/app.yaml, that wins. A trailing slash is stripped, so https://your-domain.com/ and https://your-domain.com behave identically.
  • page_identifier: Shared key to match localized versions (defaults to slug).
  • available_languages: List of supported language codes, used to create alternate links. Omitting it disables alternate links entirely — you get canonical tags only, with no warning.

The config file is optional. If origin is set in config/app.yaml you can skip it entirely and still get canonical links; you need this file only for available_languages or a custom page_identifier. The config/canonical-links.yaml shipped inside the package is documentation only — Nera reads config from your project and never merges the two.

If neither app.origin nor app_origin resolves, the plugin generates no canonical links at all and prints a warning. Earlier versions emitted <link rel="canonical" href="undefined/…"> in that case.

🧩 Usage

Include the published view in your layout head:

head
    include /vendor/plugin-canonical-links/index

The leading slash makes this root-absolute: it resolves against your views/ directory regardless of where the including file sits. It requires Nera v4.3.0+. On v4.1.x–v4.2.x, use the relative form instead — the path is then relative to the including file, so this assumes a layout in views/layouts/:

head
    include ../vendor/plugin-canonical-links/index

A bare include vendor/plugin-canonical-links/index (no leading slash) is not equivalent: from views/layouts/ it resolves to views/layouts/vendor/… and fails the build.

🛠️ Template Publishing

Copy the plugin's templates into your project:

npx nera-canonical-links

This copies index.pug and its partials/ to:

views/vendor/plugin-canonical-links/

Publishing skips if that directory already exists, so your edits are never overwritten. To pull in updated templates after an upgrade, discarding your changes to them, use npx nera-canonical-links --force.

Because the skip is on the directory, an upgrade that changes a template reaches your site only when you re-run with --force. Upgrading without it is safe — the new markup simply never appears. Diff your published copies first if you have customised them.

📊 Generated Output

For an English page with Spanish and French translations:

<link href="https://example.com/index.html" rel="canonical" />
<link href="https://example.com/es/index.html" hreflang="es" rel="alternate" />
<link href="https://example.com/fr/index.html" hreflang="fr" rel="alternate" />

The templates emit link elements only, with no class attributes — there is nothing to style, and nothing here is a CSS contract.

Data written to each page

If you write your own template instead of publishing the shipped one, these are the keys to read:

  • meta.canonicalLink{ href, rel }, where rel is always 'canonical'.
  • meta.alternateLinks — an array of { href, hreflang, rel }, where rel is always 'alternate'.

Two cases to guard for:

  • With no available_languages, meta.alternateLinks is an empty array, not absent.
  • When no origin resolves, both keys are absent entirely. The shipped partials already guard for this; a hand-written template that does not will throw during the build.

🗂️ Content Structure

To use alternate links, provide a shared identifier (e.g., slug) across translations:

pages/
├── index.md
├── es/
│   └── index.md
└── fr/
    └── index.md

Example frontmatter for each:

pages/index.md

lang: en
slug: home

pages/es/index.md

lang: es
slug: home

pages/fr/index.md

lang: fr
slug: home

Frontmatter requirements

These are rules, not just conventions in the example above:

  • A page must set lang. Without it the page still gets a canonical link, but no alternate links at all, silently.
  • Translations must set an identical page_identifier value (slug by default). Pages that omit the key are never matched to anything — they get a canonical link and an empty alternateLinks.

Pages generated by other plugins

Plugins run in the order start: → alphabetical → end:, from config/plugin-order.yaml. plugin-canonical-links sorts alphabetically before plugins that generate pages — notably plugin-tags, which creates its tag-overview pages while it runs. Those pages are created after this plugin has already finished, so they receive no canonical link at all.

If you use such a plugin, run this one at the end instead:

# config/plugin-order.yaml
plugin-order:
    - end:
          - plugin-canonical-links
          - plugin-search

config/plugin-order.yaml is honoured from Nera v4.2.0+; on earlier versions the file is ignored. Generated pages carry no frontmatter identifier, so they get a canonical link and no alternates.

🧩 Rendering Details

The plugin provides a view file that includes two partials:

  • views/index.pug

    include partials/canonical-link
    include partials/alternate-links
  • views/partials/canonical-link.pug

    if (meta.canonicalLink)
        link(href=meta.canonicalLink.href, rel=meta.canonicalLink.rel)
  • views/partials/alternate-links.pug

    if (meta.alternateLinks && meta.alternateLinks.length > 0)
        each alternate in meta.alternateLinks
            link(href=alternate.href, hreflang=alternate.hreflang, rel=alternate.rel)

Publish them (see above) and customize the copies for full control.

🧪 Development

npm install
npx vitest run
npm run lint

npm test starts Vitest in watch mode; use npx vitest run for a single pass. Includes unit and integration tests using Vitest and Pug.

🤝 Contributing

Issues and pull requests are welcome. See the Nera contributing guide for plugin development, the hook contract, and local setup.

For this repo specifically:

  • npx vitest run and npm run lint must pass (npm test is watch mode).
  • Bump the version and update CHANGELOG.md in the same commit as the change.
  • The template markup is a public contract — users publish copies into views/vendor/plugin-canonical-links/ and include them from their own layouts, so changing what the templates emit is a major bump.
  • Releases publish from CI on a pushed v* tag. Never run npm publish.

🧑‍💻 Author

Michael Becker
https://github.com/seebaermichi

🔗 Links

🧩 Compatibility

  • Nera: v4.3.0+ for the root-absolute include /vendor/… shown in Usage, which needs the Pug basedir the generator began setting in 4.3.0. The plugin itself needs nothing above the 4.x baseline — on v4.1.x–v4.2.x use the relative include. The config/plugin-order.yaml advice above needs v4.2.0+.
  • Node.js: >= 20.0.0
  • Plugin Utils: ^1.2.0npx nera-canonical-links calls publishAllTemplates, added in 1.2.0, which is what copies partials/ alongside index.pug.
  • Plugin API: exports getMetaData(), which writes per-page data.

📦 License

MIT