🔗 next-validate-link

Introduction

A tool for validating links in Markdown files

Setup

Install it.

npm i next-validate-link fast-glob

Scan available URLs, it predicts them from your file-system based routing.

import { scanURLs } from 'next-validate-link';
 
const scanned = await scanURLs({
  preset: 'next'
});

Validate Markdown files and print errors.

import fg from 'fast-glob';
import { printErrors, validateFiles } from 'next-validate-link';
 
printErrors(
  await validateFiles(await fg('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
You can use other TypeScript executors like 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 fg from 'fast-glob';
import { validateFiles } from 'next-validate-link';
 
await validateFiles(await fg('content/**/*.{md,mdx}'), {
  scanned,
  checkExternal: true,
});

Relative Urls

To support relative urls, specify a function that generates URL based on file path. This will assign a URL to each Markdown file, so that relative URLs can be resolved correctly.

import path from 'node:path';
 
await validateFiles(await fg('content/**/*.{md,mdx}'), {
  pathToUrl: (file) => {
    // just an example, it should return a pathname/url, like `/slug/of/page`
    return path.dirname(file);
  },
});

You can also point to a Markdown file:

[Test](./another-file.md)

It will be resolved into the generated URL of that Markdown file (same as pathToUrl("./another-file.md")).

[Test](/docs/another-file)

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]]'],
});

On this page