Skip to content

Repository files navigation

metalsmith-safe-links

Metalsmith plugin to process all URLs in HTML documents for sites that need subdirectory deployment support. Handles both absolute and relative URLs across all HTML elements and inline CSS styles.

URL Processing:

  • Absolute URLs with matching hostnames → Strips <protocol://hostname>, adds base path
  • Relative URLs starting with / → Adds base path (e.g., /page//my-app/page/)
  • External URLs (anchors only) → Adds target and rel attributes
  • Path-relative URLs (e.g., ./, ../) → Left unchanged

Processes URLs in all relevant HTML elements:

  • <a href> - Links (also gets target/rel for external)
  • <link href> - Stylesheets, favicons, etc.
  • <script src> - JavaScript files
  • <img src> - Images
  • <iframe src> - Embedded frames
  • <source src> - Media sources
  • <track src> - Video/audio tracks
  • <embed src> - Embedded content
  • <form action> - Form submission URLs
  • <object data> - Object data
  • <video poster> - Video posters
  • <area href> - Image map areas
  • style attributes - CSS url() functions (e.g., background-image: url(...))

This provides a comprehensive solution for sites deployed in subdirectories, handling all URL references in a single place regardless of whether they're absolute or relative.

metalsmith: plugin npm: version license: ISC coverage ESM

This package is ESM-only and requires Node >= 22. CommonJS projects on Node 22 can still consume it with require('metalsmith-safe-links') thanks to Node's require(esm) support.

Installation

npm i metalsmith-safe-links

Usage

This plugin must be used after markdown has been transformed into html

import Metalsmith from 'metalsmith';
import markdown from '@metalsmith/markdown';
import layouts from '@metalsmith/layouts';
import metalsmithSafeLinks from 'metalsmith-safe-links';

Metalsmith(import.meta.dirname)
  .use(markdown())
  .use(layouts())
  .use(
    metalsmithSafeLinks({
      hostnames: ['www.livesite.com', 'stagingsite.com'],
      basePath: 'my-app' // Optional: for sites deployed in subdirectories
    })
  )
  .build();

Options

Option Type Default Description
hostnames array [] Array of hostnames considered "local". The plugin will strip <protocol://hostname> from all links with these hostnames.
basePath string '' Base path to prepend to local links (e.g., 'my-app' for sites deployed at /my-app/).

Examples

Basic Usage (without basePath)

Absolute URLs:

<!-- Input -->
<a href="https://www.livesite.com/page/">Internal Link</a>
<a href="https://external.com/">External Link</a>
<link href="https://www.livesite.com/styles.css" rel="stylesheet">

<!-- Output -->
<a href="/page/">Internal Link</a>
<a href="https://external.com/" target="_blank" rel="noopener noreferrer">External Link</a>
<link href="/styles.css" rel="stylesheet">

Relative URLs (no changes without basePath):

<!-- Input & Output (unchanged) -->
<a href="/about/">Root-relative Link</a>
<img src="./image.jpg" alt="Path-relative Image">
<script src="../js/app.js"></script>

With Base Path

When using basePath: 'my-app', all applicable URLs are transformed:

<!-- Input -->
<link href="https://www.livesite.com/styles.css" rel="stylesheet">
<a href="https://www.livesite.com/page/">Absolute Internal Link</a>
<img src="/images/photo.jpg" alt="Root-relative Image">
<a href="/about/">Root-relative Link</a>
<script src="./local.js"></script>

<!-- Output -->
<link href="/my-app/styles.css" rel="stylesheet">
<a href="/my-app/page/">Absolute Internal Link</a>  
<img src="/my-app/images/photo.jpg" alt="Root-relative Image">
<a href="/my-app/about/">Root-relative Link</a>
<script src="./local.js"></script>  <!-- Path-relative unchanged -->

CSS url() Processing

The plugin also processes URLs within CSS url() functions in style attributes:

<!-- Input -->
<div style="background-image: url('https://www.livesite.com/bg.jpg')">Local background</div>
<div style="background-image: url('https://external.com/bg.jpg')">External background</div>
<div style="background-image: url('/relative/image.jpg')">Root-relative background</div>
<p style="background: url(https://www.livesite.com/icon.png) no-repeat; color: red;">Complex styles</p>

<!-- Output (with basePath: 'my-app') -->
<div style="background-image: url('/my-app/bg.jpg')">Local background</div>
<div style="background-image: url('https://external.com/bg.jpg')">External background</div>
<div style="background-image: url('/my-app/relative/image.jpg')">Root-relative background</div>
<p style="background: url(/my-app/icon.png) no-repeat; color: red;">Complex styles</p>

CSS Processing Features:

  • Handles single quotes, double quotes, and unquoted URLs
  • Preserves original formatting and spacing
  • Processes multiple url() functions within complex styles
  • Skips data URLs and hash references (e.g., #pattern)
  • Works with any CSS property using url() (background-image, border-image, etc.)

Key behaviors:

  • Absolute URLs with matching hostnames → stripped and get base path
  • Root-relative URLs (/page/) → get base path prepended
  • Path-relative URLs (./, ../) → unchanged (handled by browser)

This enables sites deployed in subdirectories like https://example.com/my-app/ to work correctly with all assets and links, regardless of URL format.

Debug

To enable debug logs, set the DEBUG environment variable to metalsmith-safe-links:

metalsmith.env('DEBUG', 'metalsmith-safe-links*');

CLI usage

To use this plugin with the Metalsmith CLI, add metalsmith-safe-links to the plugins key in your metalsmith.json file:

{
  "plugins": [
    {
      "metalsmith-safe-links": {
        "hostnames": ["www.livesite.com", "stagingsite.com"],
        "basePath": "my-app"
      }
    }
  ]
}

Authors

werner@glinka.co

License

Code released under the ISC license.

About

Metalsmith plugin to process all URLs in HTML documents for sites that need subdirectory deployment support. Handles both absolute and relative URLs across all HTML elements and inline CSS styles.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages