MCPRemote MCP server for Cursor and Claude Code, on your existing API key→ Read the docs
QuikSEO.com

Documentation

Site Contract: Enforce Your Own SEO Guidelines

A site contract declares your own conventions, such as URL form, title pattern, head order and heading case, in a quikseo.config.json file. QuikSEO checks every audited page and every build against it and reports each assertion as CTR-<id>, in a separate section that never changes the score.

How a Site Contract Works

Each website can have one active contract, kept as numbered versions with a hash. Every audit stores the hash of the version it applied, so a report always names the rules it was checked against.

  1. Write the contract on the website's Site contract page, start from the preset, import a file, or accept conventions deduced from your pages.
  2. Save it: the new version applies from the next audit of each page.
  3. Read the results in the Site contract group of every report, and in the CLI or GitHub Action output.

Assertion Types

Every assertion has an id (lowercase letters, digits and hyphens), a type, a severity you choose (critical, error, warning or info) and optional parameters. Nothing in a contract runs code: selectors and expressions are data, checked when you save.

TypeParametersFails whenSource
urlPolicytrailingSlash (never, always, any), lowercase, noQuery, noIndexHtml, maxLengthThe final URL breaks one of the rules; the root path is exempt from the slash rule.Google Search Central (Q39)
titlePatternmustMatch, mustNotMatch (regular expressions), minLength, maxLengthThe title is missing or breaks a pattern or length.Google Search Central (S10)
headOrderorder: charset, viewport, title, description, canonical, og, twitter, icons, preload, stylesheet, jsonld, scriptThe listed elements present in the head appear in another order.WHATWG (Q55)
forbiddenone of selector, htmlComments, jsonLdTypeThe page has a matching element, an HTML comment, or JSON-LD of that type.Project decision
requiredselector and count, or min and maxThe number of matching elements is outside the range.Project decision
headingCasestyle (title, sentence), levels, exceptionsA heading of those levels is not written in the style; brands and acronyms keep their spelling.Project decision
canonicalPolicyrequired, absolute, noQuery, sameHost, selfReferencingThe first canonical breaks one of the rules.Google Search Central (S12)

Scope Patterns

An assertion applies to every page unless it has a scope: one URL path pattern or a list of them. * matches inside one path segment and ** across segments, so /blog/* covers /blog/post but not /blog/2026/post, and /docs/** covers every page under /docs/.

Title Case Rules

In Title Case, the first word, the last word, the first word after a colon and every other word are capitalized, except a, an, the, and, but, or, nor, for, so, yet, as, at, by, in, of, on, to, up and vs inside the heading. Both parts of a hyphenated word are capitalized. List brand spellings in exceptions.

Limits and Safety

The limits keep every check fast and predictable, whatever the contract says.

  • At most 100 assertions and 64 KB per contract.
  • Selectors and regular expressions of at most 300 characters; a selector must parse and an expression must compile.
  • An expression that exhausts the backtracking budget on a test string, such as (a+)+$, is refused when you save.
  • Regular expressions are written without delimiters and match Unicode text.

The Preset

The preset is a starting contract that most sites can adopt as it is or edit; load it into the editor from the Site contract page.

{
    "version": 1,
    "assertions": [
        {
            "id": "url-no-trailing-slash",
            "type": "urlPolicy",
            "severity": "warning",
            "scope": [],
            "title": "URLs without a trailing slash",
            "trailingSlash": "never",
            "lowercase": true,
            "noIndexHtml": true
        },
        {
            "id": "title-length",
            "type": "titlePattern",
            "severity": "warning",
            "scope": [],
            "title": "Title between 30 and 65 characters",
            "minLength": 30,
            "maxLength": 65
        },
        {
            "id": "head-order",
            "type": "headOrder",
            "severity": "info",
            "scope": [],
            "title": "Head order",
            "order": [
                "charset",
                "viewport",
                "title",
                "description",
                "canonical"
            ]
        },
        {
            "id": "one-h1",
            "type": "required",
            "severity": "error",
            "scope": [],
            "title": "Exactly one h1",
            "selector": "h1",
            "count": 1
        },
        {
            "id": "one-canonical",
            "type": "required",
            "severity": "error",
            "scope": [],
            "title": "Exactly one canonical",
            "selector": "link[rel~=\"canonical\"]",
            "count": 1
        },
        {
            "id": "no-generator",
            "type": "forbidden",
            "severity": "info",
            "scope": [],
            "title": "No generator meta tag",
            "selector": "meta[name=\"generator\"]"
        },
        {
            "id": "no-inline-style",
            "type": "forbidden",
            "severity": "info",
            "scope": [],
            "title": "No style attributes",
            "selector": "[style]"
        },
        {
            "id": "no-html-comments",
            "type": "forbidden",
            "severity": "info",
            "scope": [],
            "title": "No HTML comments",
            "htmlComments": true
        },
        {
            "id": "no-faq-schema",
            "type": "forbidden",
            "severity": "warning",
            "scope": [],
            "title": "No FAQPage structured data",
            "jsonLdType": "FAQPage"
        },
        {
            "id": "canonical-clean",
            "type": "canonicalPolicy",
            "severity": "error",
            "scope": [],
            "title": "Absolute canonical on the same host, without a query string",
            "absolute": true,
            "noQuery": true,
            "sameHost": true
        },
        {
            "id": "headings-title-case",
            "type": "headingCase",
            "severity": "warning",
            "scope": [],
            "title": "Headings in Title Case",
            "style": "title",
            "levels": [
                1,
                2,
                3
            ]
        }
    ]
}

Contracts Deduced From Your Site

QuikSEO proposes assertions from the conventions that at least 80% of your audited pages already follow, with the number of pages that follow each one and up to three exceptions. Nothing applies until you accept a proposal and choose its severity; accepted proposals are added to the active contract as a new version.

Use the Contract in CI

Keep quikseo.config.json in your repository and the CLI applies it to every build, so a pull request that breaks a convention fails before it is deployed. quikseo contract pull writes the website's active contract to the file, and quikseo contract push saves the file as a new version.

Related Documentation