Nuxt module
sitemap.xml, robots.txt and typed JSON-LD out of the box. One command to install, no config required.
useJsonLduseRanklintIgnoreA Nuxt module, a DevTools tab and a CI crawler that fails the pipeline on SEO regressions. Any stack.
24 pages · playwright chromium · 7.1s
Ranklint reads the HTML your server actually returns to a crawler and names the exact problem: an empty title, a broken canonical, a heading hierarchy that skips a level, a one-way hreflang pair. Three surfaces, one set of rules.
sitemap.xml, robots.txt and typed JSON-LD out of the box. One command to install, no config required.
useJsonLduseRanklintIgnoreIssues, heading structure and meta tags for the current route — right on the dev server, while you write code.
live checkslight and darkCrawls your deployed site through Chromium. Works with any stack — Nuxt not required.
auditdiffwatchThe same rules, running in this tab. Paste the markup of a page and the linter answers right here, with no server and no install.
<!doctype html> <html lang="en"> <head> <title>Pricing</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta property="og:title" content="Pricing"> <meta property="og:description" content="Plans and prices for teams of every size."> <meta property="og:image" content="https://example.com/og/pricing.png"> </head> <body> <h1>Pricing plans for growing teams</h1> <h3>What the team plan includes</h3> <img src="/plans.png"> <h1>Frequently asked questions</h1> </body> </html>
The SEO tab in Nuxt DevTools shows the current route the way a crawler sees it: the list of issues, the heading tree and every meta tag. It refreshes when the host page navigates and when the DOM changes, and there is a Rescan button.
Every rule has an id and a level: error, warn, info/off. Levels live in a config file next to your code, not in somebody's head.
export default defineRanklintConfig({
rules: {
'canonical:required': 'error',
'http:ttfb-budget': ['warn', { p75: 600 }],
'images:no-lazy-above-fold': 'off',
},
})A quarterly crawl turns into a backlog everyone forgets. Ranklint checks every pull request: audit produces a JSON report, diff compares it against the base branch and fails the pipeline only on new errors. Existing debt never blocks a merge.
| level | rule | URL | what happened |
|---|---|---|---|
| error | meta:title-required | /bugs/title-missing | Page has no <title> |
| error | meta:description-required | /bugs/description-missing | Page has no meta description |
| error | headings:single-h1 | /bugs/h1-multiple | Page has 2 <h1> elements, expected exactly 1 |
| warn | images:alt-required | /bugs/images-bad | Image has no alt attribute |
| warn | images:dimensions-required | /bugs/images-bad | Image has no explicit width/height |
| warn | images:no-lazy-above-fold | /bugs/images-bad | Image in the viewport uses loading="lazy" |
markdownjsonjunithtmlgitlabgithubjunit goes into your runner's reports, gitlab is a Code Quality report, and github turns every finding into a pull request annotation.
$ ranklint audit --url https://preview --reporter json --output report.json
$ ranklint diff --base main --current report.json --reporter markdownCode review misses SEO regressions — in a browser everything looks fine. The exit code doesn't.
Each zone has its own list of checks and an owner: a zone you own is crawled in full, an external one only gets a HEAD check on the links that point at it.
The crawler speaks HTTP and renders pages in Chromium through Playwright. It does not care what serves them — Next, Django, WordPress or static files on a CDN.
The ranklint audit --url https://prod --mode monitor run crawls production on a schedule, keeps every report in history and tells you when something breaks.
Real-user LCP, CLS and INP for the whole origin land next to the linter's findings in the same report.
LCPCLSINPURL Inspection reports Google's indexing verdict, the coverage state and rich-results issues for the first ten pages of the crawl.
A Slack or Telegram message on every new error; the report history and the dashboard live on GitHub or GitLab Pages.
Add one step after the monitor and every scheduled run publishes a static site to GitHub or GitLab Pages. The team sees errors by page, trends and history without opening a terminal.

$ ranklint dashboard \
--dir .ranklint/reports --out public--logo--brand-name--accent--run-urlYour logo, name and accent color instead of ours. --run-url adds a Run audit button that opens your CI pipeline.
How to set up the dashboardAn SEO linter reads the HTML your site actually returns and reports the things that cost you traffic: a missing title, a canonical pointing at a redirect, a heading level that jumps from h1 to h3, a hreflang pair that only links one way. It is the same idea as ESLint, applied to the markup search engines read instead of the JavaScript you ship.
Most SEO work dies in a spreadsheet. Someone runs a crawler once a quarter, exports a list of 400 issues, and the list rots while the site keeps shipping. A linter inverts that: the crawl runs on every pull request, the report is a diff, and the only thing you have to look at is what changed since the last green build. ranklint stores the JSON report as a build artifact, compares the current run against it and posts the difference as a comment.
See how audit and diff fit togetherRegressions are rarely dramatic. A refactor drops the canonical from one template, a CMS migration leaves 40 pages without a description, an experiment adds noindex and nobody removes it. Those changes pass code review because nothing looks broken in the browser. A crawl that runs with your tests catches them in the same hour they were introduced. Severity is per rule, so title length can stay a warning while a missing canonical stops the merge.
Configure rules and severitiesThe exit code makes the decision instead of a person.
In a Nuxt 4 app the module covers the parts that are pure plumbing. It serves sitemap.xml from your routes, robots.txt that knows the difference between staging and production, and JSON-LD that is validated against Schema.org while you develop, so a broken Product or FAQPage block shows up as a console warning instead of a silent loss of rich results. Each block is opt out, and a disabled block is never registered, so it costs nothing at runtime.
Read the module overviewThe crawler talks HTTP, not framework internals. Point it at a URL and it renders each page in Chromium, which means client rendered content is judged the way a search engine judges it, and pages that only exist after hydration are reported as an indexing problem. One domain can be split into zones when different apps live under different paths, and profiles keep the slow checks off the fast pipeline.
Start with the CLIranklint diff is for: it fails a build only on errors that are new relative to the base branch, while a bare audit exits 1 on any error at all. Run diff in the pipeline and accumulated debt stays in the report.useRanklintIgnore inside the page. There is no per-URL override anywhere else: set the rule to off in rules, or drop the address from the crawl with crawl.ignore.Install the module, add two steps to your pipeline — from there the exit code decides.