Rules
seolint checks every page with the rules below. Each rule belongs to a built-in plugin, and runs unless your config turns it off.
Configure a rule
Set rules in the rules field of seolint.config.ts:
import * as config from "seolint/config";
export default config.define({
rules: {
"html.favicon": false, // turn a rule off
"heading.order": "error", // change its severity
"title.content.length": { max: 70 }, // change some options
"link.external.limit": ["warn", { max: 10 }], // both
"og.*": "error", // every rule that matches a pattern
},
});
| Setting | Effect |
|---|---|
false |
Turns the rule off. |
true |
Turns the rule on, with its default severity and options. |
"error" or "warn" |
Turns the rule on, with that severity. |
{ ... } |
Turns the rule on, and overrides some of its options. |
["warn", { ... }] |
Sets the severity and some options. |
- Severity: an
errormakes the CLI exit with1. Awarnis reported, but does not change the exit code. - Patterns: a name with
*sets every rule that matches. Patterns apply first, then exact names, so{ "og.*": false, "og.title": true }keeps onlyog.title. - Unknown names are an error, so a typo does not go unnoticed.
define()also checks rule names and options as you type. - The page URL: some rules compare against the page's own URL. For a file, it is the
hostplus the file's path, withoutindex.html. Without ahost, these rules are skipped.
Run seolint --rules to list every rule with its current setting.
html
Rules about the document as a whole.
| Rule | Default | Checks |
|---|---|---|
html.doctype |
error | A page starts with <!doctype html>. |
html.lang |
error | The <html> element has a valid lang attribute. |
html.charset |
error | A page declares the utf-8 encoding near the top. |
html.favicon |
warn | A page links to a favicon. |
html.refresh |
warn | A page does not redirect with a meta refresh. |
title
Rules about the <title> element, which search results show as the link text.
| Rule | Default | Checks |
|---|---|---|
title.exists |
error | A page has a <title> element in its <head>. |
title.single |
error | A page has only one <title> element. |
title.content.html |
error | The title has no HTML tags in its text. |
title.content.undefined |
error | The title does not include the word "undefined". |
title.content.null |
error | The title does not include the word "null". |
title.content.length |
warn | The title length is within a range of characters. |
description
Rules about <meta name="description">, which search results often show below the title.
| Rule | Default | Checks |
|---|---|---|
description.exists |
error | A page has a <meta name="description"> tag. |
description.single |
error | A page has only one description tag. |
description.content.empty |
error | The description is not empty. |
description.content.html |
error | The description has no HTML tags in its text. |
description.content.title |
warn | The description shares at least one meaningful word with the title. |
description.content.undefined |
error | The description does not include the word "undefined". |
description.content.null |
error | The description does not include the word "null". |
description.content.length |
warn | The description length is within a range of characters. |
canonical
Rules about <link rel="canonical">, which tells search engines the main URL of a page.
| Rule | Default | Checks |
|---|---|---|
canonical.exists |
error | A page has a <link rel="canonical"> tag. |
canonical.single |
error | A page has only one canonical link. |
canonical.href.exists |
error | The canonical link has a non-empty href. |
canonical.href.absolute |
error | The canonical href is an absolute URL. |
canonical.href.lowercase |
warn | The canonical href has no uppercase letters. |
canonical.href.https |
error | The canonical href uses https://. |
canonical.href.fragment |
error | The canonical href has no #fragment. |
canonical.href.match |
error | The canonical href uses the site hostname. |
canonical.href.self |
warn | The canonical href points at the page itself. |
canonical.head |
error | The canonical link is inside <head>. |
viewport
Rules about <meta name="viewport">, which controls how a page fits on phones.
| Rule | Default | Checks |
|---|---|---|
viewport.exists |
error | A page has a <meta name="viewport"> tag. |
viewport.single |
error | A page has only one viewport tag. |
viewport.content.empty |
error | The viewport content is not empty. |
viewport.content.device |
error | The viewport content includes "width=device-width". |
viewport.content.scale |
error | The viewport content includes "initial-scale=1". |
viewport.content.zoom |
error | The viewport does not stop users from zooming. |
heading
Rules about headings, which show search engines how a page is organized.
| Rule | Default | Checks |
|---|---|---|
heading.exists |
error | A page has an <h1>. |
heading.single |
warn | A page has only one <h1>. |
heading.order |
warn | Headings start at <h1> or <h2>, and do not skip levels, such as an <h4> after an <h2>. |
robots
Rules about <meta name="robots"> and <meta name="googlebot">.
| Rule | Default | Checks |
|---|---|---|
robots.noindex |
error | A page does not ask search engines to leave it out of results. |
robots.nofollow |
error | A page does not ask search engines to ignore its links. |
image
Rules about <img> elements.
| Rule | Default | Checks |
|---|---|---|
image.alt.exists |
error | An <img> has an alt attribute. alt="" is allowed, for decorative images. |
image.alt.content.html |
error | The alt text has no HTML tags. |
image.alt.content.undefined |
error | The alt text does not include the word "undefined". |
image.alt.content.null |
error | The alt text does not include the word "null". |
link
Rules about <a> elements. A link is internal when it is relative, or when its hostname matches the host. Without a host, every absolute link counts as external. Except for link.href.exists and link.text.exists, links to a section of the same page, such as #install, and links to mailto: and javascript: are skipped, and each href is checked once per page. Links to a section of the same page also skip link.text.vague.
| Rule | Default | Checks |
|---|---|---|
link.href.exists |
error | A link has an href attribute. |
link.href.empty |
error | A link href is not empty. |
link.internal.trailing |
error | An internal link ends with a slash for a folder page, and without one for a file page. |
link.internal.lowercase |
warn | An internal link has no uppercase letters. |
link.internal.nofollow |
error | An internal link does not use rel="nofollow". |
link.internal.pretty |
off | An internal link does not end with ".html". |
link.internal.absolute |
off | An internal link starts with /, //, or a protocol. |
link.internal.https |
error | An internal link uses https:// or a root-relative path. |
link.external.limit |
off | A page has at most a number of external links. |
link.blank.noopener |
off | A link with target="_blank" uses rel="noopener". |
link.blank.noreferrer |
off | An external link with target="_blank" uses rel="noreferrer". |
link.text.exists |
error | A link has text, an aria-label, or an image with alt text. |
link.text.vague |
warn | A link text describes its target, unlike "click here". |
mixed
| Rule | Default | Checks |
|---|---|---|
mixed.content |
error | An https:// page loads no resources over http://. |
hreflang
Rules about <link rel="alternate" hreflang>, which point search engines to translated versions of a page. They only run on pages with such links.
| Rule | Default | Checks |
|---|---|---|
hreflang.code |
error | An hreflang value is a valid language code, or x-default. |
hreflang.absolute |
error | An hreflang link uses an absolute URL. |
hreflang.default |
warn | Pages with hreflang links also have an x-default link. |
hreflang.self |
error | Pages with hreflang links also link to themselves. |
hreflang.return |
error | A page that an hreflang link points to links back to the page. |
og
Rules about Open Graph tags, which social networks and chat apps use for link previews. They do not change search ranking, so they default to warn. Set "og.*": "error" to require them.
| Rule | Default | Checks |
|---|---|---|
og.title |
warn | A page has an og:title, for link previews. |
og.description |
warn | A page has an og:description, for link previews. |
og.image |
warn | A page has an og:image, for link previews. |
og.image.secure |
warn | The og:image is an absolute https:// URL. |
og.url |
warn | A page has an og:url that matches its canonical URL. |
| Rule | Default | Checks |
|---|---|---|
twitter.card |
warn | A page has a valid twitter:card, for link previews. |
jsonld
Rules about JSON-LD, the structured data in <script type="application/ld+json"> that Google reads for rich results.
| Rule | Default | Checks |
|---|---|---|
jsonld.valid |
error | Each JSON-LD script is valid JSON, with an @context and an @type. |
site
Rules that compare pages across the whole site. They run when you lint a folder, or URLs, with seolint public or run(). They do not run for lint(), which checks one page.
site.title.uniqueandsite.description.uniquerun for files and URLs. They skip pages withnoindex, or with a canonical link to another URL.- The other
site.*rules read the built files, so they only run for folders, which they treat as site roots. Most of them also need ahost, to match pages to their URLs.
Two rules of other plugins also compare pages: link.internal.trailing reads the built files, and hreflang.return compares the linted pages.
| Rule | Default | Checks |
|---|---|---|
site.title.unique |
error | Every page has a different title. |
site.description.unique |
error | Every page has a different description. |
site.link.exists |
error | Internal links point to files that exist in the build. |
site.canonical.exists |
error | A canonical link to the site points to a page of the build. |
site.page.linked |
warn | Every page has a link from another page. |
site.robots |
warn | A robots.txt exists, and does not block every crawler. |
site.robots.noindex |
error | robots.txt does not block a page with noindex, so search engines see it. |
site.sitemap |
warn | A sitemap exists, and lists every page. |
site.sitemap.entries |
warn | A sitemap lists only https pages of the build that search engines can index and crawl. |