@nera-static/plugin-canonical-links 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -0
- package/README.md +30 -5
- package/bin/publish-template.js +18 -0
- package/index.js +30 -15
- package/package.json +9 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [2.1.0] - 2026-07-19
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- no longer emits `<link rel="canonical" href="undefined/...">` on every page
|
|
13
|
+
when no origin is configured. That markup was worse than none — it told
|
|
14
|
+
search engines the authoritative URL was a broken string, and nothing was
|
|
15
|
+
printed to warn about it. The plugin now warns once and leaves pages
|
|
16
|
+
untouched
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `publish-template` command (`npx nera-canonical-links`), so the shipped
|
|
21
|
+
templates can be copied into `views/vendor/` like every other plugin.
|
|
22
|
+
Previously there was no publish path at all and the README worked around it
|
|
23
|
+
by including straight out of `node_modules/`
|
|
24
|
+
- `--force` to re-publish over existing templates, discarding local edits
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- `@nera-static/plugin-utils` range widened to `^1.2.0`, which is what makes
|
|
29
|
+
`partials/` publish alongside `index.pug`
|
|
30
|
+
- config is resolved per call rather than at module load
|
|
31
|
+
- README: `app_origin` was documented as overriding `app.origin`. It is the
|
|
32
|
+
other way round — `app.origin` wins. The docs now match the code
|
|
33
|
+
- README: documents that omitting `available_languages` silently disables
|
|
34
|
+
alternate links
|
|
35
|
+
|
|
36
|
+
### Removed
|
|
37
|
+
|
|
38
|
+
- dead `if (!config)` guard — `getConfig` returns `{}`, so it never fired
|
|
39
|
+
|
|
40
|
+
## [2.0.1] - 2025-07-19
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- Complete CHANGELOG.md with full version history
|
|
45
|
+
- Enhanced package.json keywords for better discoverability
|
|
46
|
+
- Compatibility section in README.md
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Improved documentation with Nera v4.1.0+ compatibility notes
|
|
51
|
+
- Enhanced keywords for NPM search optimization
|
|
52
|
+
- Better structured development and deployment information
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- Documentation consistency and clarity improvements
|
|
57
|
+
|
|
58
|
+
## [2.0.0] - 2025-07-19
|
|
59
|
+
|
|
60
|
+
### Breaking Changes
|
|
61
|
+
|
|
62
|
+
- Updated for compatibility with Nera v4.1.0+
|
|
63
|
+
- Requires Node.js >= 18
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- Support for Nera's enhanced plugin architecture
|
|
68
|
+
- Improved error handling and logging compatibility
|
|
69
|
+
- Full compatibility with Nera's parallel plugin loading
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- Updated dependencies to latest versions
|
|
74
|
+
- Enhanced test coverage and reliability
|
|
75
|
+
- Improved documentation and development setup
|
|
76
|
+
|
|
77
|
+
### Technical
|
|
78
|
+
|
|
79
|
+
- Compatible with Nera's new plugin data merging system
|
|
80
|
+
- Optimized for parallel execution performance
|
|
81
|
+
- Enhanced development tooling with Husky and ESLint
|
|
82
|
+
|
|
83
|
+
## [1.x] - Previous Versions
|
|
84
|
+
|
|
85
|
+
### Features
|
|
86
|
+
|
|
87
|
+
- Canonical link generation for SEO optimization
|
|
88
|
+
- Multi-language alternate link support
|
|
89
|
+
- Configurable page identifier and language detection
|
|
90
|
+
- Pug template integration for head section
|
|
91
|
+
|
|
92
|
+
### Configuration
|
|
93
|
+
|
|
94
|
+
- Flexible origin URL configuration
|
|
95
|
+
- Support for multiple available languages
|
|
96
|
+
- Customizable page identifier field
|
|
97
|
+
- YAML-based configuration system
|
|
98
|
+
|
|
99
|
+
[2.0.1]: https://github.com/seebaermichi/nera-plugin-canonical-links/compare/v2.0.0...v2.0.1
|
|
100
|
+
[2.0.0]: https://github.com/seebaermichi/nera-plugin-canonical-links/compare/v1.0.0...v2.0.0
|
package/README.md
CHANGED
|
@@ -39,17 +39,42 @@ available_languages:
|
|
|
39
39
|
- fr
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
- `app_origin`: The canonical base URL
|
|
42
|
+
- `app_origin`: The canonical base URL. Used as a fallback — if `origin` is
|
|
43
|
+
set in `config/app.yaml`, **that wins**.
|
|
43
44
|
- `page_identifier`: Shared key to match localized versions (defaults to `slug`).
|
|
44
|
-
- `available_languages`: List of supported language codes
|
|
45
|
+
- `available_languages`: List of supported language codes, used to create
|
|
46
|
+
alternate links. **Omitting it disables alternate links entirely** — you
|
|
47
|
+
get canonical tags only, with no warning.
|
|
48
|
+
|
|
49
|
+
If neither `app.origin` nor `app_origin` resolves, the plugin generates no
|
|
50
|
+
canonical links at all and prints a warning. Earlier versions emitted
|
|
51
|
+
`<link rel="canonical" href="undefined/…">` in that case.
|
|
52
|
+
|
|
53
|
+
## 🛠️ Template Publishing
|
|
54
|
+
|
|
55
|
+
Copy the plugin's templates into your project:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx nera-canonical-links
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This copies `index.pug` and its `partials/` to:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
views/vendor/plugin-canonical-links/
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Publishing **skips** if that directory already exists, so your edits are never
|
|
68
|
+
overwritten. To pull in updated templates after an upgrade, discarding your
|
|
69
|
+
changes to them, use `npx nera-canonical-links --force`.
|
|
45
70
|
|
|
46
71
|
## 📄 Usage in Templates
|
|
47
72
|
|
|
48
|
-
Include the
|
|
73
|
+
Include the published view in your layout head:
|
|
49
74
|
|
|
50
75
|
```pug
|
|
51
76
|
head
|
|
52
|
-
include /
|
|
77
|
+
include vendor/plugin-canonical-links/index
|
|
53
78
|
```
|
|
54
79
|
|
|
55
80
|
This will generate:
|
|
@@ -121,7 +146,7 @@ The plugin provides a view file that includes two partials:
|
|
|
121
146
|
link(href=alternate.href, hreflang=alternate.hreflang, rel=alternate.rel)
|
|
122
147
|
```
|
|
123
148
|
|
|
124
|
-
|
|
149
|
+
Publish them (see above) and customize the copies for full control.
|
|
125
150
|
|
|
126
151
|
## 🧪 Development
|
|
127
152
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { publishAllTemplates } from '@nera-static/plugin-utils'
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const pluginName = 'plugin-canonical-links'
|
|
9
|
+
const sourceDir = path.resolve(__dirname, '../views/')
|
|
10
|
+
const force = process.argv.includes('--force')
|
|
11
|
+
|
|
12
|
+
const result = publishAllTemplates({
|
|
13
|
+
pluginName,
|
|
14
|
+
sourceDir,
|
|
15
|
+
force,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
process.exit(result ? 0 : 1)
|
package/index.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import { getConfig } from '@nera-static/plugin-utils'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
const config = getConfig(HOST_CONFIG_PATH)
|
|
4
|
+
// Resolved per call rather than at module scope: the host project's cwd is
|
|
5
|
+
// what matters, and resolving lazily is what makes this testable.
|
|
6
|
+
function getHostConfig() {
|
|
7
|
+
return getConfig(path.resolve(process.cwd(), 'config/canonical-links.yaml'))
|
|
8
|
+
}
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The canonical base URL. `app.origin` from config/app.yaml wins over the
|
|
12
|
+
* plugin's own `app_origin` — the opposite of what the README claimed
|
|
13
|
+
* before 2.1.0.
|
|
14
|
+
*/
|
|
15
|
+
function getOrigin(data, config) {
|
|
16
|
+
return data.app?.origin || config.app_origin
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
function getAlternates(pageMeta, data) {
|
|
19
|
+
function getAlternates(pageMeta, data, config, origin) {
|
|
16
20
|
const pageIdentifier = config.page_identifier || 'slug'
|
|
17
21
|
const availableLanguages = config.available_languages || []
|
|
18
22
|
const multiLangCanonicals = []
|
|
@@ -27,7 +31,7 @@ function getAlternates(pageMeta, data) {
|
|
|
27
31
|
|
|
28
32
|
if (relCanonical) {
|
|
29
33
|
multiLangCanonicals.push({
|
|
30
|
-
href: `${
|
|
34
|
+
href: `${origin}${relCanonical.meta.href}`,
|
|
31
35
|
hreflang: availableLang,
|
|
32
36
|
rel: 'alternate',
|
|
33
37
|
})
|
|
@@ -38,15 +42,26 @@ function getAlternates(pageMeta, data) {
|
|
|
38
42
|
return multiLangCanonicals
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
function getCanonical(meta,
|
|
45
|
+
function getCanonical(meta, origin) {
|
|
42
46
|
return {
|
|
43
|
-
href: `${
|
|
47
|
+
href: `${origin}${meta.href}`,
|
|
44
48
|
rel: 'canonical',
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export function getMetaData(data) {
|
|
49
|
-
|
|
53
|
+
const config = getHostConfig()
|
|
54
|
+
const origin = getOrigin(data, config)
|
|
55
|
+
|
|
56
|
+
// Without an origin every page used to get
|
|
57
|
+
// <link rel="canonical" href="undefined/blog/post.html">, which is worse
|
|
58
|
+
// than emitting nothing at all — it hands search engines a broken URL as
|
|
59
|
+
// the authoritative one. Bail out loudly instead.
|
|
60
|
+
if (!origin) {
|
|
61
|
+
console.warn(
|
|
62
|
+
'⚠️ plugin-canonical-links: no origin configured, so no canonical links were generated.\n' +
|
|
63
|
+
' Set `origin` in config/app.yaml, or `app_origin` in config/canonical-links.yaml.'
|
|
64
|
+
)
|
|
50
65
|
return data.pagesData
|
|
51
66
|
}
|
|
52
67
|
|
|
@@ -54,8 +69,8 @@ export function getMetaData(data) {
|
|
|
54
69
|
return {
|
|
55
70
|
content,
|
|
56
71
|
meta: Object.assign({}, meta, {
|
|
57
|
-
canonicalLink: getCanonical(meta,
|
|
58
|
-
alternateLinks: getAlternates(meta, data),
|
|
72
|
+
canonicalLink: getCanonical(meta, origin),
|
|
73
|
+
alternateLinks: getAlternates(meta, data, config, origin),
|
|
59
74
|
}),
|
|
60
75
|
}
|
|
61
76
|
})
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nera-static/plugin-canonical-links",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A plugin for Nera static site generator to create canonical links in to be used in the head.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"publish-template": "node ./bin/publish-template.js",
|
|
7
8
|
"lint": "eslint .",
|
|
8
9
|
"test": "vitest"
|
|
9
10
|
},
|
|
@@ -11,10 +12,15 @@
|
|
|
11
12
|
"exports": {
|
|
12
13
|
".": "./index.js"
|
|
13
14
|
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"nera-canonical-links": "./bin/publish-template.js"
|
|
17
|
+
},
|
|
14
18
|
"files": [
|
|
19
|
+
"bin",
|
|
15
20
|
"config",
|
|
16
21
|
"index.js",
|
|
17
|
-
"views"
|
|
22
|
+
"views",
|
|
23
|
+
"CHANGELOG.md"
|
|
18
24
|
],
|
|
19
25
|
"keywords": [
|
|
20
26
|
"nera",
|
|
@@ -38,7 +44,7 @@
|
|
|
38
44
|
},
|
|
39
45
|
"homepage": "https://github.com/seebaermichi/nera-plugin-canonical-links#readme",
|
|
40
46
|
"dependencies": {
|
|
41
|
-
"@nera-static/plugin-utils": "^1.0
|
|
47
|
+
"@nera-static/plugin-utils": "^1.2.0"
|
|
42
48
|
},
|
|
43
49
|
"engines": {
|
|
44
50
|
"node": ">=18"
|