Introduction
A tool for validating links in Markdown files
Setup
Install it.
npm i next-validate-link
Scan available URLs, it predicts them from your file-system based routing.
import { scanURLs } from 'next-validate-link';
const scanned = await scanURLs({
preset: 'next'
});
Choose a preset according to your framework:
next
waku
tanstack-start
astro
nuxt
react-router
: also pass your router config to theroutes
option.
Validate Markdown files and print errors.
import { printErrors, validateFiles, readFiles } from 'next-validate-link';
printErrors(
await validateFiles(await readFiles('content/**/*.{md,mdx}'), {
scanned,
}),
// exit with code 1 if errors detected
true,
);
The validateFiles
function accepts a list of file paths or file objects containing its path and content.
You can run this script during lint process, or before builds.
bun ./lint.ts
tsx
.Configurations
Meta
Specify allowed queries and fragments of a page.
import { scanURLs } from 'next-validate-link';
const scanned = await scanURLs({
meta: {
'/': {
// allowed fragment strings
hashes: ['fragment'],
// allowed queries
queries: [
{
search: 'fumadocs',
},
],
},
},
});
Static Params
Use populate
to populate dynamic routes into static.
import { scanURLs } from 'next-validate-link';
const scanned = await scanURLs({
populate: {
// you can generate them too! (e.g. from file system or CMS)
'(home)/blog/[slug]': [{ value: 'blog-1' }, { value: 'blog-2' }],
'docs/[...slug]': [
{
value: ['hello', 'world'],
// allowed fragment strings
hashes: ['fragment'],
// allowed queries
queries: [
{
search: 'fumadocs',
},
],
},
],
},
});
Each param object indicates a page, allowed hashes and queries can be specified individually.
Multiple Params
When you have multiple dynamic routes, you need to use an object instead.
import { scanURLs } from 'next-validate-link';
const scanned = await scanURLs({
populate: {
'[lang]/blog/[slug]': [
{
value: {
lang: 'en',
slug: 'blog-1',
},
},
{
value: {
lang: 'cn',
slug: 'blog-1',
},
},
],
},
});
External Urls
To validate external urls, enable it.
import { validateFiles, readFiles } from 'next-validate-link';
await validateFiles(await readFiles('content/**/*.{md,mdx}'), {
scanned,
checkExternal: true,
});
Relative Paths
To check relative paths such as:
[Test](./another-file.md)
You can specify the checkRelativePaths
option, allowed values listed below:
exists
Check if the referenced file exists.
import { readFiles, validateFiles } from "next-validate-link";
await validateFiles(await readFiles('content/**/*.{md,mdx}'), {
checkRelativePaths: 'exists',
});
as-url
Resolve the file path as URL, you need to specify a function that generates URL based on file path.
import path from "node:path";
import { readFiles, validateFiles } from "next-validate-link";
await validateFiles(await readFiles('content/**/*.{md,mdx}'), {
checkRelativePaths: 'as-url',
pathToUrl: (file) => {
// just an example, it should return a pathname/url, like `/slug/of/page`
return path.dirname(file);
},
});
Then, the relative path will be checked as a regular URL.
false
(default)
Ignore relative paths.
Relative URLs
By default, it checks relative URLs such as:
[Test](./my-page)
When a relative URL is detected, you need to define either:
file.url
in input file objects.pathToUrl
option.baseUrl
option.
Or you can disable the check:
import { readFiles, validateFiles } from "next-validate-link";
await validateFiles(await readFiles('content/**/*.{md,mdx}'), {
checkRelativeUrls: false,
});
Utilities
readFiles
Read files for given glob patterns, and output a list of file objects.
import { readFiles } from 'next-validate-link';
const files = await readFiles('content/docs/**/*.{md,mdx}');
You can use it in conjunction with validateFiles
.
Advanced
pages
By default, it looks for the pages from your file system. You can override the obtained pages by passing an array of urls.
This also allows you to parse available pages from non-Next.js routing.
import { scanURLs } from 'next-validate-link';
const scanned = await scanURLs({
pages: ['/', '(home)/overview', 'docs/[[...slug]]'],
});