Validate frontmatter Markdown SEO metadata in NodeJS
How to parse and validate markdown with YAML headers and assert for required meta data lengths and properties.
Many websites today are built using static site builders and markdown with embed data: the JAM Stack. Markdown alone is useful for writing content. Adding Frontmatter YAML headers to Markdown allows us to embed meta data for static site builders. To validate this content to SEO we will share a test.
Hugo setup
Say you have a website built with Hugo. You might want to read all the content and check that the title and description properties of the markdown are of an appropriate length. We can do so using Jest and Node JS.
Install deps
We need a few node dependencies for this examples:
npm install glob-promise glob front-matter
Test with Jest
Let's write a test that loads all the Markdown files in the content
folder and parses their markdown frontmatter. We then return a result for various tests such as description length. We use Jest to run the tests:
/**
* Test to load content, read the markdown frontmatter and validate SEO recommended lengths etc
*/
const { join } = require("path");
const glob = require("glob-promise");
const { readFile } = require("fs/promises")
const fm = require('front-matter')
const CONTENT_DIR = join(__dirname, "..", "content")
const States = {
OK: "OK",
FAIL: "FAIL",
TOO_LONG: "TOO_LONG",
TOO_SHORT: "TOO_SHORT",
}
function testSize(string, min, max) {
let res = States.OK;
if (!string) {
res = States.TOO_SHORT;
}
else if (string.length < min) {
res = States.TOO_SHORT;
}
else if (string.length > max) {
res = States.TOO_LONG;
}
return {
min, max, result: res, length: string?.length ?? 0
}
}
async function getContentFiles() {
return glob(join(CONTENT_DIR, '**/*.md'))
}
async function scoreMeta(path) {
const content = await readFile(path, "utf-8")
let data;
try {
data = fm(content)
} catch (e) {
throw new Error("Read error " + path, e.message)
}
const { title, description } = data.attributes
const h1Missing = data.body?.indexOf("# ") > -1 ? States.OK : States.FAIL
return {
path: path,
metaDescriptionLength: testSize(description, 110, 160),
metaTitleLength: testSize(title, 50, 60),
h1Missing
}
}
describe("seo of content", () => {
test("meta description length", async () => {
const files = await getContentFiles()
expect(files.length).toBeGreaterThan(0);
const scores = await Promise.all(files.map(file=> scoreMeta(file)));
for(let score of scores) {
const metaDescription = score.metaDescriptionLength
expect(metaDescription.result, `META DESCRIPTION ${metaDescription.result}: ${score.path} found
${metaDescription.length} wanted between ${metaDescription.min} - ${metaDescription.max}`).toEqual(States.OK)
}
})
})
Execute jest
to run the test.