|
| 1 | +--- |
| 2 | +title: Broken Links |
| 3 | +--- |
| 4 | + |
| 5 | +# Broken Links ((tag|VitePress plugin|blue)) ((tag|markdown-it|orange)) |
| 6 | + |
| 7 | +[[readingTime]] |
| 8 | + |
| 9 | +[](https://github.com/binarynoir/vitepress-broken-links) [](https://www.npmjs.com/package/@binarynoir/vitepress-broken-links) [](https://github.com/binarynoir/vitepress-broken-links/actions/workflows/ci.yml) [](https://github.com/binarynoir/vitepress-broken-links/blob/main/LICENSE) |
| 10 | + |
| 11 | +`@binarynoir/vitepress-broken-links` stops one dead link from taking down your whole build. VitePress checks every internal link against your pages, and a link to a page that doesn't exist normally stops the build with `1 dead link(s) found`. With this plugin the link is shown as text instead, with a wavy underline and a popover that says what is wrong and which line of which file it is on. Links that work are left exactly as they are. |
| 12 | + |
| 13 | +It is built for docs sites with many contributors, where a mistyped or outdated link on one page shouldn't block deploying all the others. |
| 14 | + |
| 15 | +## See it live |
| 16 | + |
| 17 | +The second and third links below point at pages that don't exist, on purpose. Without the plugin this page would fail the whole site build. With it, the build finishes and logs a warning for each one, and the links render like this. Hover one, or click or tap it, to see the popover: |
| 18 | + |
| 19 | +```md |
| 20 | +Start with [the overview](/plugins/), then read [the setup guide](/guide/setup) |
| 21 | +and [part two of this page](./broken-links-part-2.md). |
| 22 | +``` |
| 23 | + |
| 24 | +::: info On the page |
| 25 | +Start with [the overview](/plugins/), then read [the setup guide](/guide/setup) |
| 26 | +and [part two of this page](./broken-links-part-2.md). |
| 27 | +::: |
| 28 | + |
| 29 | +The build log names each one, with its file and line: |
| 30 | + |
| 31 | +```txt |
| 32 | +(!) Broken link /guide/setup in plugins/broken-links.md:25 is shown as text instead of a link. |
| 33 | +``` |
| 34 | + |
| 35 | +That warning is the plugin working as designed. |
| 36 | + |
| 37 | +## Install |
| 38 | + |
| 39 | +```sh |
| 40 | +npm install --save-dev @binarynoir/vitepress-broken-links |
| 41 | +``` |
| 42 | + |
| 43 | +## Use |
| 44 | + |
| 45 | +Register it in VitePress's `markdown.config` hook, the same place as any other markdown-it plugin: |
| 46 | + |
| 47 | +```ts |
| 48 | +// .vitepress/config.mts |
| 49 | +import { defineConfig } from 'vitepress'; |
| 50 | +import { markdownBrokenLinks } from '@binarynoir/vitepress-broken-links'; |
| 51 | + |
| 52 | +export default defineConfig({ |
| 53 | + markdown: { |
| 54 | + config(md) { |
| 55 | + md.use(markdownBrokenLinks); |
| 56 | + }, |
| 57 | + }, |
| 58 | +}); |
| 59 | +``` |
| 60 | + |
| 61 | +Or wrap the config with `withBrokenLinks` from `@binarynoir/vitepress-broken-links/vitepress`. It keeps any `markdown.config` you already have, including one set by another `withX()` wrapper. |
| 62 | + |
| 63 | +```ts |
| 64 | +import { withBrokenLinks } from '@binarynoir/vitepress-broken-links/vitepress'; |
| 65 | + |
| 66 | +export default withBrokenLinks( |
| 67 | + defineConfig({ |
| 68 | + // ...your config |
| 69 | + }), |
| 70 | +); |
| 71 | +``` |
| 72 | + |
| 73 | +Then turn on the underline and popovers in your theme: |
| 74 | + |
| 75 | +```ts |
| 76 | +// .vitepress/theme/index.ts |
| 77 | +import DefaultTheme from 'vitepress/theme'; |
| 78 | +import { enableBrokenLinkHints } from '@binarynoir/vitepress-broken-links/client'; |
| 79 | + |
| 80 | +export default { |
| 81 | + extends: DefaultTheme, |
| 82 | + enhanceApp() { |
| 83 | + enableBrokenLinkHints(); |
| 84 | + }, |
| 85 | +}; |
| 86 | +``` |
| 87 | + |
| 88 | +The popovers work the same way as [HTML Escape's hints](/plugins/html-escape/#hints), and the two run side by side on this site. |
| 89 | + |
| 90 | +## What counts as dead |
| 91 | + |
| 92 | +Exactly what VitePress itself would report. The plugin reads your running VitePress config, so it uses the same page list, `rewrites`, HTML files in `public/`, and `ignoreDeadLinks` entries. Left alone are: |
| 93 | + |
| 94 | +- links to pages that exist, relative or absolute, with or without `.md` |
| 95 | +- external links such as `https://…`, and `#anchors` on the same page |
| 96 | +- links to files such as `.pdf` or `.zip` |
| 97 | +- anything matched by your `ignoreDeadLinks` setting |
| 98 | + |
| 99 | +Links to `localhost` are dead to VitePress too, since they only work on your own machine. They get their own message, which points to `ignoreDeadLinks: 'localhostLinks'` if you mean to keep them. |
| 100 | + |
| 101 | +## Why not `ignoreDeadLinks`? |
| 102 | + |
| 103 | +VitePress's built-in [`ignoreDeadLinks`](https://vitepress.dev/reference/site-config#ignoredeadlinks) option also keeps dead links from failing the build, but it does that by skipping the check, not by handling the link: |
| 104 | + |
| 105 | +- **Readers still get a 404.** An ignored link is published as a normal link. It looks like it works until someone clicks it. This plugin removes the link, so it can't send anyone to a missing page, and the popover tells them why. |
| 106 | +- **Authors aren't told.** `ignoreDeadLinks: true` turns the check off for the whole site, so dead links no longer show up anywhere in the build output. This plugin still logs each one with its file and line, and marks it on the page. |
| 107 | +- **You'd have to know the links in advance.** The string, RegExp and function forms only skip links you have already listed. They suit known exceptions, but not a typo someone adds next week. |
| 108 | +- **It's all or nothing per site.** With this plugin, a page that must never ship a dead link can opt out in its frontmatter, and VitePress's check applies to that page again. |
| 109 | + |
| 110 | +The two work together. Links matched by `ignoreDeadLinks` stay links, so use it for the exceptions you mean to keep, and let this plugin handle the mistakes. If you want a dead link to block a release, for example as a CI gate on pull requests, leave this plugin out of that build. |
| 111 | + |
| 112 | +## Options |
| 113 | + |
| 114 | +`markdownBrokenLinks(md, options)` and `withBrokenLinks(config, options)` take: |
| 115 | + |
| 116 | +| Option | Default | Description | |
| 117 | +| ------- | ------- | --------------------------------------------------------------------------------------------- | |
| 118 | +| `hints` | `true` | Attach the explanation, file and line to each broken link. `false` shows the text plainly. | |
| 119 | +| `warn` | `true` | Log a warning for each broken link while rendering, in the same place VitePress reports them. | |
| 120 | + |
| 121 | +`enableBrokenLinkHints(options)` takes: |
| 122 | + |
| 123 | +| Option | Default | Description | |
| 124 | +| -------------- | ------- | ------------------------------------------------------------------------------- | |
| 125 | +| `hover` | `true` | Show the popover on mouse hover. `false` for click and tap only. | |
| 126 | +| `hoverDelay` | `300` | Milliseconds the mouse rests on a hint before the popover shows. | |
| 127 | +| `injectStyles` | `true` | Inject the default styles. `false` lets you style `.broken-link-hint` yourself. | |
| 128 | + |
| 129 | +The colors follow the VitePress theme and can be changed with the `--broken-link-hint-error` CSS variable. |
| 130 | + |
| 131 | +## Per-page settings |
| 132 | + |
| 133 | +The `brokenLinks` frontmatter key overrides the options for one page: |
| 134 | + |
| 135 | +| Frontmatter | What it does | |
| 136 | +| ------------------------------- | --------------------------------------------------------------------------- | |
| 137 | +| `brokenLinks: { hints: false }` | Shows broken links on the page as plain text, with no underline or popover. | |
| 138 | +| `brokenLinks: { warn: false }` | Stops logging warnings for the page. | |
| 139 | +| `brokenLinks: false` | Hands the page back to VitePress, so a dead link on it fails the build. | |
| 140 | + |
| 141 | +There is no live page for `brokenLinks: false`: a dead link on it would stop this site from building, which is the point. |
| 142 | + |
| 143 | +## Limits |
| 144 | + |
| 145 | +- **Only Markdown links are checked.** A raw `<a href>` in HTML isn't checked by VitePress either, so it is left alone. |
| 146 | +- **A missing heading isn't a dead link.** `./page#nope` on a page that exists stays a link, as it does in VitePress. |
| 147 | +- **It needs VitePress.** The page list comes from the running VitePress process, so under plain markdown-it the plugin does nothing. |
| 148 | + |
| 149 | +[Source and full README on GitHub](https://github.com/binarynoir/vitepress-broken-links) |
0 commit comments