A Metalsmith plugin that applies a custom permalink pattern to files, and renames them so that they're nested properly for static sites (converting about.html
into about/index.html
).
NPM:
npm install @metalsmith/permalinks
Yarn:
yarn add @metalsmith/permalinks
import { dirname } from 'path'
import { fileURLToPath } from 'url'
import Metalsmith from 'metalsmith'
import permalinks from '@metalsmith/permalinks'
const __dirname = dirname(fileURLToPath(import.meta.url))
Metalsmith(__dirname).use(
permalinks({
pattern: ':title'
})
)
The pattern
can contain a reference to any piece of metadata associated with the file by using the :PROPERTY
syntax for placeholders.
If no pattern is provided, the files won't be remapped, but the permalink
metadata key will still be set, so that you can use it for outputting links to files in the template.
The pattern
can also be set as such:
metalsmith.use(
permalinks({
// original options would act as the keys of a `default` linkset,
pattern: ':title',
date: 'YYYY',
// each linkset defines a match, and any other desired option
linksets: [
{
match: { collection: 'blogposts' },
pattern: 'blog/:date/:title',
date: 'mmddyy'
},
{
match: { collection: 'pages' },
pattern: 'pages/:title'
}
]
})
)
The pattern option can also contain optional placeholders with the syntax :PROPERTY?
. If the property is not defined in a file's metadata, it will be replaced with an empty string ''
. For example the pattern :category?/:title
applied to a source directory with 2 files:
|
|
would generate the file tree:
build
├── category1/with-category/index.html
└── no-category/index.html
By default any date will be converted to a YYYY/MM/DD
format when using in a permalink pattern, but you can change the conversion by passing a date
option:
metalsmith.use(
permalinks({
pattern: ':date/:title',
date: 'YYYY'
})
)
It uses moment.js to format the string.
You can finetune how a pattern is processed by providing custom slug options. By default slugify is used and patterns will be lowercased.
You can pass custom slug options:
metalsmith.use(
permalinks({
slug: {
replacement: '_',
lower: false
}
})
)
The following makes everything snake-case but allows '
to be converted to -
metalsmith.use(
permalinks({
slug: {
remove: /[^a-z0-9- ]+/gi,
lower: true,
extend: {
"'": '-'
}
}
})
)
If your pattern parts contain special characters like :
or =
, specifying slug.strict
as true
is a quick way to remove them:
metalsmith.use(
permalinks({
slug: {
lower: true,
strict: true
}
})
)
If the result is not to your liking, you can replace the slug function altogether. For now only the js version of syntax is supported and tested.
metalsmith.use(
permalinks({
pattern: ':title',
slug: require('transliteration').slugify
})
)
There are plenty of other options on npm for transliteration and slugs. https://www.npmjs.com/browse/keyword/transliteration.
When this plugin rewrites your files to be permalinked properly, it will also duplicate sibling files so that relative links like css/style.css
will be preserved nicely. You can turn this feature off by setting the relative
option to false
.
For example for this source directory:
src/
css/
style.css
post.html
Here's what the build directory would look like with relative
on:
build/
post/
index.html
css/
style.css
css/
style.css
And here's with relative
off:
build/
post/
index.html
css/
style.css
relative
can also be set to folder
, which uses a strategy that considers files in folder as siblings if the folder is named after the html file.
For example using the folder
strategy with this source directory:
src/
post.html
post/
image.jpg
Here's what the build directory would look like with relative
set to folder
:
build/
index.html
image.jpg
A file can be ignored by the permalinks plugin if you pass the permalink: false
option to the yaml metadata of a file.
This is useful for hosting a static site on AWS S3, where there is a top level error.html
file and not an error/index.html
file.
For example, in your error.md file:
---
template: error.html
title: error
permalink: false
---
Using the permalink
property in a file's front-matter, its permalink can be overridden. This can be useful for transferring
projects over to Metalsmith where pages don't follow a strict permalink system.
For example, in one of your pages:
---
title: My Post
permalink: "posts/my-post"
---
Use indexFile
to define a custom index file.
metalsmith.use(
permalinks({
indexFile: 'alt.html'
})
)
Normally you should take care to make sure your source files do not permalink to the same target.
When URI clashes occur nevertheless, the build will halt with an error stating the target file conflict.
metalsmith.use(
permalinks({
duplicates: 'error'
})
)
There are 3 other possible values for the duplicates
option: index
will add an -<index>
suffix to other files with the same target URI, , overwrite
will silently overwrite previous files with the same target URI.
The third possibility is to provide your own function to handle duplicates, with the signature:
function paginateDupes(targetPath, files, filename, options) => {
let target,
counter = 0,
postfix = ''
while (files[target]) {
postfix = `/${++counter}`
target = path.join(`${targetPath}${postfix}`, options.indexFile)
}
return target
}
Return an error in the custom duplicates handler to halt the build.
The example above is a variant of the index
value, where 2 files targeting the URI gallery
will be written to gallery/1/index.html
and gallery/2/index.html
.
Note: The duplicates
option combines the unique
and duplicatesFail
options of version < 2.4.1. Specifically, duplicatesFail:true
maps to duplicates:'error'
, unique:true
maps to duplicates:'index'
, and unique:false
or duplicatesFail:false
map to duplicates:'overwrite'
.
To enable debug logs, set the DEBUG
environment variable to @metalsmith/permalinks
:
metalsmith.env('DEBUG', '@metalsmith/permalinks*')
Alternatively you can set DEBUG
to @metalsmith/*
to debug all Metalsmith core plugins.
To use this plugin with the Metalsmith CLI, add @metalsmith/permalinks
to the plugins
key in your metalsmith.json
file:
{
"plugins": [
{
"permalinks": {
"pattern": ":title"
}
}
]
}