Scoreling

76 checks · every weight public

Every check we run

Each one explains what it looks for, why it matters, and how to fix it. Nothing here is a secret formula: the score is these checks and their weights, added up.

SEO

45 checks · 35% of the total

Title, description, canonical, headings, structured data, the sitemap, the robots rules that quietly remove a page from search, and whether AI answer engines may quote it.

Page is not blocked by a robots meta tag

A noindex directive removes the page from search entirely. It is the most damaging single line on a page, and it is nearly always left behind by accident after a staging deploy.

Fix Remove noindex from <meta name="robots">, or narrow it to the pages that genuinely should stay out of the index.

High impact · weight 10

Page has a title

The <title> element is the headline Google shows in search results and the label the browser puts on the tab. A page without one is listed under a machine-picked fallback that nobody chose.

Fix Add a unique <title> inside <head> describing this page specifically, not the whole site.

High impact · weight 10

No noindex in the X-Robots-Tag header

X-Robots-Tag does the same job as the robots meta tag but travels in the HTTP response, so it is invisible in the page source. A page can look perfectly indexable while the server quietly forbids it.

Fix Remove the noindex directive from the X-Robots-Tag header in the web server or application config.

High impact · weight 10

Page has a meta description

The meta description is the snippet under the title in search results. Without one Google assembles a snippet from whatever text it finds, which is often a navigation menu or a cookie banner.

Fix Add <meta name="description" content="..."> summarising what the visitor gets on this page.

High impact · weight 8

No insecure subresources

An https page that pulls scripts, styles, or images over http has those requests blocked or downgraded by the browser. Layout and behaviour break with no server-side error to show for it.

Fix Serve every subresource over https, or let the asset pipeline emit protocol-relative paths.

High impact · weight 8

robots.txt lets Google crawl the page

A Disallow rule in robots.txt stops Googlebot from fetching the page at all, so everything else on this report stops mattering: the page can only appear in results as a bare URL with no description, or not at all.

Fix Remove or narrow the Disallow rule that matches this path, or add a longer Allow rule for it.

High impact · weight 8

Page has exactly one H1

The H1 states what the page is about. Zero leaves the topic unstated; several compete with each other and make the page read as a list of unrelated sections.

Fix Keep one H1 for the page subject and demote the rest to H2.

High impact · weight 8

Page declares a canonical URL

Without a canonical, every URL variant that reaches this page (tracking parameters, trailing slash, uppercase path) can be indexed as a separate page, splitting the ranking signal between duplicates.

Fix Add <link rel="canonical" href="..."> pointing at the absolute preferred URL of this page.

Medium impact · weight 7

Images have alt text

Alt text is how a screen reader describes an image and how a search engine understands it. Missing alt attributes are an accessibility defect and lost image-search traffic at the same time.

Fix Give every content image a short alt describing what it shows.

Medium impact · weight 6

Title length is in range

Below about 30 characters the strongest on-page signal is left half unused; past about 60 Google truncates the title in the results page and the tail is wasted.

Fix Rewrite the title to roughly 30-60 characters, front-loading the term the page should rank for.

Medium impact · weight 6

Canonical points at this page

A canonical aimed somewhere else tells Google to index that other URL instead of this one. Done by accident, usually by copying a template, it removes the page from search while everything still looks fine on screen.

Fix Point the canonical at this page's own absolute URL, unless it is genuinely a duplicate of another page.

Medium impact · weight 5

Document declares its language

The lang attribute tells screen readers which pronunciation rules to use and helps search engines serve the page to the right audience. Without it a screen reader may read English text with the wrong phonetics.

Fix Set the language on the root element, for example <html lang="en">.

Medium impact · weight 5

Meta description length is in range

Descriptions shorter than about 120 characters leave the snippet half empty; past about 160 the end is cut off mid-sentence.

Fix Aim for 120-160 characters and put the reason to click in the first half.

Medium impact · weight 5

AI crawlers may read the page

ChatGPT search, Perplexity, Claude and Google's AI features quote pages their crawlers are allowed to read. A robots.txt rule that blocks GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot or Google-Extended keeps the page out of those answers. Blocking the training crawlers can be a deliberate choice; blocking all of them is rarely intended.

Fix Remove the Disallow rules for the AI crawlers you want to be quoted by, or add a group for them that allows this path.

Low impact · weight 4

AI crawlers are not refused at the edge

robots.txt can welcome AI crawlers while the CDN or firewall in front of the site turns them away. Cloudflare has blocked AI crawlers by default on new sites since July 2025, and many security plugins do the same. We request the page as GPTBot, OAI-SearchBot, ClaudeBot and PerplexityBot and look for refusals and challenge pages. Our requests do not come from those companies' addresses, so a rule that checks the address as well may treat the real crawlers differently.

Fix In Cloudflare, open Security, then Bots, and review "Block AI bots" and "AI Crawl Control": allow at least OAI-SearchBot and PerplexityBot, which fetch pages to cite them.

Low impact · weight 4

One address serves the site

The same page reachable at http and https, or with and without www, is several copies of the site as far as search engines are concerned. They pick one, split links and signals between the copies, and sometimes pick the wrong one. The other addresses should redirect permanently to the one you use.

Fix Redirect http to https and the www or bare variant to your preferred host with a 301, in the server or CDN.

Low impact · weight 4

Content is in the HTML, not only in JavaScript

GPTBot, ClaudeBot and PerplexityBot download a page's JavaScript but do not run it, so a page that builds its text in the browser is blank to them. Google does run JavaScript, but later and not always. This counts the words in the HTML as it arrives, before any script runs.

Fix Render the page's main content on the server: server-side rendering or static generation in Next.js, Nuxt, SvelteKit or Angular, or prerendering for a single-page app.

Low impact · weight 4

Heading levels are not skipped

Screen readers and crawlers read headings as an outline. Jumping from H2 straight to H4 implies a missing level, so the outline reports a section that does not exist.

Fix Step heading levels one at a time.

Low impact · weight 4

Page links to the rest of the site

Internal links are how crawlers reach the rest of the site and how ranking signal flows between pages. A page with none is a dead end that hoards whatever authority it earns.

Fix Link to related pages in the body copy, not only from the shared navigation.

Low impact · weight 4

Structured data parses as JSON

A JSON-LD block with a trailing comma or an unescaped quote is discarded whole. The rich result it was meant to earn silently never appears, and nothing in the page looks broken.

Fix Fix the JSON syntax in the failing block, then confirm it in Google's Rich Results Test.

Low impact · weight 4

Local business markup is complete

For a business people visit or call, LocalBusiness markup with a name, an address and a phone number is how Google connects the page to its map listing and local results. A page that lists a phone number but does not say whose it is leaves that to guesswork.

Fix Add a LocalBusiness JSON-LD block (or the closest subtype, such as Dentist or Plumber) with name, address as a PostalAddress, and telephone, matching your Google Business Profile exactly.

Low impact · weight 4

Open Graph tags are present

Open Graph decides what a link to this page looks like when someone shares it on Slack, WhatsApp, LinkedIn, or Facebook. Missing tags turn a share into a bare URL with no image, which measurably costs clicks.

Fix Add og:title, og:description, and og:image meta tags, with the image at least 1200x630.

Low impact · weight 4

Structured data has what rich results require

Markup only earns stars, prices, FAQs or breadcrumbs in search results when it carries the properties Google requires for that type: an Article needs a headline, an image and a publication date; a Product needs an offer with a price and currency, a review or a rating. Markup that is present but incomplete earns nothing.

Fix Add the missing properties listed, then confirm the page in Google's Rich Results Test.

Low impact · weight 4

hreflang alternates are absolute URLs

Google requires fully qualified URLs in hreflang annotations. Relative hrefs are ignored without any error, so the language targeting simply never happens and nobody notices.

Fix Replace relative hreflang hrefs with absolute ones, including scheme and host.

Low impact · weight 3

The page's main topic is in its title and H1

The words a page repeats most are what it is about, as far as a reader skimming it can tell. When those words do not appear in the title and the main heading, the page promises one thing in search results and talks about another, and it ranks for neither.

Fix Put the page's main topic, in the words your visitors use, into the title tag and the H1.

Low impact · weight 3

Page does not redirect with a meta refresh

A <meta http-equiv="refresh"> that sends visitors elsewhere is a redirect search engines treat as weaker and slower than a real one, and screen readers and the back button handle it badly. A refresh that only reloads the page throws away whatever the visitor was reading.

Fix Replace the meta refresh with a 301 redirect on the server.

Low impact · weight 3

Phone number matches the markup

Local search trusts a business whose name, address and phone agree everywhere they appear. A telephone in the markup that differs from the number the page asks people to call is a contradiction Google has to resolve, and it may resolve it against you.

Fix Use one phone number: the same in the LocalBusiness markup, in the page's tel: links and in your Google Business Profile.

Low impact · weight 3

Site has a reachable XML sitemap

A sitemap is the list of pages a site wants crawled. Without one Google finds pages only by following links, so new or thinly linked pages can take weeks to be discovered.

Fix Publish a sitemap at /sitemap.xml (most CMSs and frameworks generate one) and name it in robots.txt with a `Sitemap:` line.

Low impact · weight 3

Search and AI answers may quote the page

nosnippet, max-snippet and data-nosnippet tell Google how much of the page it may show. They also govern Google's AI Overviews and AI Mode: a page that forbids snippets is not quoted there either, and a very short max-snippet leaves it little to quote.

Fix Remove nosnippet and max-snippet:0 from the robots meta tag and the X-Robots-Tag header, raise a small max-snippet or set it to -1, and keep data-nosnippet for fragments such as prices or legal text, not the main content.

Low impact · weight 3

Missing pages answer 404

When an address that does not exist answers 200, search engines index error pages as real ones, and a broken link on another site looks like it works. Sending every missing page to the homepage is treated the same way: Google calls it a soft 404. We ask the site for a page that cannot exist and look at the answer.

Fix Make the server or CMS return status 404 (or 410 for pages removed on purpose) for unknown addresses, with a helpful error page in the body.

Low impact · weight 3

Page describes itself with structured data

schema.org markup tells search engines what the page is: an article, a product with a price, a business with an address. Without it Google infers that from the text, and rich results such as stars, prices and FAQs are not available at all. It is often sold as a way into AI answers; measured, it is not: a 2025 Ahrefs study of 1,885 pages found no effect on AI citations, and AI assistants fetching a page live do not read JSON-LD. Add it for Google's rich results.

Fix Add a JSON-LD block with the schema.org type that matches the page (Article, Product, LocalBusiness, FAQPage...) and check it in Google's Rich Results Test.

Low impact · weight 3

Title and H1 are related but not identical

The title is written for the search results page and the H1 for the visitor who already clicked. Making them byte-identical wastes a second chance to describe the page, while sharing no vocabulary at all suggests the two disagree about the topic.

Fix Keep the shared subject in both, but let the title carry the search phrasing and the H1 the human phrasing.

Low impact · weight 3

Page has enough text to rank

There is no minimum word count in Google's rules, but a page with only a sentence or two gives search engines and AI answers little to understand, quote or rank. Pages that rank for competitive searches usually explain the subject in a few hundred words at least.

Fix Say more about what the page offers: who it is for, what is included, answers to the questions buyers ask.

Low impact · weight 3

Article says when it was published and updated

Search engines and AI assistants favour recent, dated content: pages cited in AI answers are measurably fresher than the average Google result. An article with no machine-readable date looks undated, one last touched years ago looks stale, and a modified date earlier than the published one is a contradiction.

Fix Put datePublished and dateModified in the Article markup (or article:published_time and article:modified_time meta tags), keep them true, and update dateModified only when the content really changes.

Low impact · weight 2

Facts in the markup are also on the page

A phone number or price that exists only inside JSON-LD is invisible to visitors, and to the AI assistants that read pages live: tests of ChatGPT, Claude, Perplexity and Gemini found none of them reading JSON-LD when fetching a page. Google also expects markup to describe what the page shows.

Fix Show the phone number and price in the page text as well, in the same form as in the markup.

Low impact · weight 2

Address is shown on the page

An address that lives only in the markup is invisible to the visitor, and Google weighs what the page shows over what it declares. The street and postcode people can read are also what confirms the page belongs to the business on the map.

Fix Show the full address in the page itself, usually in the footer or on the contact section, written the same way as in the markup and your Google Business Profile.

Low impact · weight 2

Phone number can be tapped to call

Most local searches happen on a phone. A number written as plain text has to be copied or retyped; a tel: link calls it in one tap.

Fix Wrap the phone number in a link: <a href="tel:+15550100100">(555) 010-0100</a>.

Low impact · weight 2

Opening hours are in the markup

Hours are among the first things people check before they call or visit. Stated in the LocalBusiness markup, they can be read by search engines and assistants instead of guessed from the page's text.

Fix Add openingHoursSpecification (or openingHours) to the LocalBusiness block, with the same hours as your Google Business Profile.

Low impact · weight 2

Title names the city

Local searches name a place ("dentist portland"), and the title is the strongest on-page signal of what a page is about. A business page whose title leaves out its city leaves that match to the pages that say it.

Fix Put the city from your address in the title, for example "Family Dentist in Portland | Smile Co".

Low impact · weight 2

The address reaches the page in at most one redirect

Every redirect is a round trip before the page even starts to load, and every hop is a place where link value and crawl budget leak. A chain like http to https to www to a trailing slash usually means redirects were added one at a time and never merged.

Fix Point every old address straight at the final one, in a single 301, and update internal links to the final address so they need no redirect at all.

Low impact · weight 2

Twitter card type is declared

Without a card type, X renders a shared link as plain text even when Open Graph tags exist. It is one line of markup that decides whether the share carries an image.

Fix Add <meta name="twitter:card" content="summary_large_image"> alongside the Open Graph tags.

Low impact · weight 2

URL is short and readable

Long parameter-laden URLs get truncated in results, are harder to share, and multiply into near-duplicate variants of the same page.

Fix Shorten the path, use hyphens rather than underscores between words, and drop parameters that do not change the content.

Low impact · weight 2

Site publishes an llms.txt

llms.txt is a proposed plain-text summary of a site written for language models: what it is, and links to the pages that matter. It is a convention, not a standard, and no major AI engine has confirmed reading it, so it weighs almost nothing here. It is cheap to add and harmless.

Fix Publish /llms.txt: a # title, a one-line > summary, then sections of markdown links to your key pages.

Low impact · weight 1

Page links to the business on Google Maps

A link to the Maps listing, or an embedded map, gives visitors directions in one tap and ties the page to the listing its reviews and hours live on.

Fix Link to your Google Maps listing (Share, then Copy link, in Google Maps) near the address, or embed the map from Share, then Embed a map.

Low impact · weight 1

Page links to the business's social profiles

Links to the business's own profiles, or sameAs in its Organization markup, tie the site to the same entity elsewhere on the web. Search engines and AI assistants use those connections to be sure who the site belongs to, and visitors use them to check the business is real.

Fix Link your Instagram, LinkedIn, Facebook or YouTube profiles from the footer, and list them as sameAs in the Organization or LocalBusiness markup.

Low impact · weight 1

Best practices

14 checks · 15% of the total

Valid markup, HTTPS and its certificate, security headers, JavaScript errors in the console, and Lighthouse's own best practices audit.

Viewport meta tag is present

Without it mobile browsers render the page at desktop width and shrink it to fit, which fails Google's mobile usability bar and leaves text unreadable on a phone. Editor linters such as webhint (Edge Tools in VS Code) report the same problem as "A 'viewport' meta element was not specified."

Fix Add <meta name="viewport" content="width=device-width, initial-scale=1"> to <head>.

High impact · weight 8

HTML parses without errors

Browsers repair broken markup silently, and each one repairs it slightly differently. Unclosed and misnested tags are where a layout that works in one browser collapses in another.

Fix Fix the reported positions: usually an unclosed tag, a stray closing tag, or an attribute missing its quote.

Medium impact · weight 7

Element ids are unique

An id must appear once per document. Duplicates break label-to-input pairing, in-page anchors, and any script that looks an element up by id, and every one of those failures is silent.

Fix Rename the repeated ids so each is used once, or switch to a class where the value was never meant to be unique.

Medium impact · weight 6

Page is served over valid HTTPS

Browsers mark plain http pages as not secure, and an expired certificate replaces the whole page with a warning that almost nobody clicks through. Certificates now last 90 days or less, so a renewal that silently stopped working takes a site down within weeks. Protocols older than TLS 1.2 are refused by current browsers.

Fix Serve the page over HTTPS with automatic renewal (Let's Encrypt through certbot, or a CDN that manages the certificate), and check the renewal job actually runs.

Medium impact · weight 6

Character encoding is declared

With no declared charset the browser guesses, and the guess is wrong often enough that accented characters, quotes, and emoji turn into mojibake for some visitors and not others.

Fix Put <meta charset="utf-8"> first inside <head>, or send it in the Content-Type response header.

Medium impact · weight 5

Lighthouse best practices score

Lighthouse's best practices audit covers what makes a page trustworthy to a browser: HTTPS everywhere, no deprecated APIs, no errors in the console, images shown at their real aspect ratio, a valid doctype and charset.

Fix Work through the failing audits listed.

Low impact · weight 4

Page uses the HTML5 doctype

A missing or legacy doctype drops the browser into quirks mode, where box sizing and layout follow rules from the 1990s. The page then differs from every modern stylesheet assumption.

Fix Make the very first line <!DOCTYPE html>.

Low impact · weight 4

Response sends the standard security headers

A handful of response headers switch on protections the browser already has: HSTS keeps visitors on HTTPS, a Content-Security-Policy limits where scripts may come from, nosniff stops files being run as the wrong type, frame protection stops the page being embedded to trick clicks, and Referrer-Policy and Permissions-Policy limit what leaks and what the page may ask for. Security scanners and clients' IT teams grade sites on exactly this list.

Fix Add the missing headers in the web server or CDN, for example Strict-Transport-Security: max-age=31536000, X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin.

Low impact · weight 4

No JavaScript errors in the console

An error logged while the page loads usually means a script stopped halfway: a menu that does not open, a form that does not submit, an analytics tag that records nothing. Visitors never see the message, only the broken behaviour.

Fix Open the browser console on the page and fix the first error listed; later ones are often caused by it.

Low impact · weight 3

No deprecated presentational tags

Tags like <center> and <font> were removed from the standard. Browsers still honour them today, but they mix styling into the markup and no longer respond to CSS the way the rest of the page does.

Fix Replace them with CSS: text-align, font-family, font-size, and their friends.

Low impact · weight 3

Page declares a favicon

The favicon is how a user finds this tab among twenty others and how the site appears in bookmarks and history. Without a declared one the browser requests /favicon.ico and usually gets a 404.

Fix Add <link rel="icon" href="/favicon.ico"> or an SVG equivalent to <head>.

Low impact · weight 2

Styling lives in stylesheets, not attributes

Inline style attributes cannot be cached, overridden by a stylesheet, or covered by a Content Security Policy that forbids inline styles. In quantity they also bloat every HTML response.

Fix Move repeated inline styles into CSS classes and keep style attributes for values genuinely computed at runtime.

Low impact · weight 2

Links that open a new tab use rel=noopener

A page opened with target="_blank" can reach back to the page that opened it and send it elsewhere. Current browsers assume noopener, but older ones and some in-app browsers do not, and saying it costs nothing.

Fix Add rel="noopener" (or noreferrer) to every link with target="_blank".

Low impact · weight 1

Email addresses are not left for spam bots

Bots crawl the web collecting every address written in a page or a mailto: link, and those mailboxes fill with spam within weeks. A contact form, or an address the page assembles with a little JavaScript, keeps the inbox usable.

Fix Replace the address with a contact form, or put it together in JavaScript so it is not in the HTML as one string.

Low impact · weight 1

Accessibility

5 checks · 15% of the total

Google's Lighthouse accessibility run (contrast, names, labels, landmarks) plus what we read in the HTML ourselves: form labels, link text, icon buttons, zoom.

Lighthouse accessibility score

Google runs the axe accessibility rules against the page as a phone renders it: colour contrast, names on controls and links, labels on form fields, alt text, headings and landmarks. The score is the weighted share of those rules the page passes. Accessibility is also law in more places each year, including the European Accessibility Act since June 2025.

Fix Start with the failing rules listed: they are ordered by how much each one weighs.

High impact · weight 10

Buttons have accessible names

A button whose only content is an icon announces itself to a screen reader as just "button", which tells the user nothing about what pressing it will do.

Fix Give each icon-only control an aria-label, or put visually hidden text inside the button.

Medium impact · weight 5

Form fields have labels

A screen reader announces a field by its label. Without one it says "edit text" and the user has to guess what goes in it. A placeholder is not a label: it disappears as soon as someone types, and many screen readers do not read it.

Fix Put a <label for="id"> next to each field, or wrap the field in its label.

Medium impact · weight 5

Links have discernible text

A link that holds only an icon or an image without alt text is announced as "link" followed by its address, or nothing at all. Search engines read link text too: it is how they learn what the target page is about.

Fix Give icon links an aria-label ("Follow us on Instagram"), and give linked images an alt that says where the link goes.

Low impact · weight 4

Users can zoom the page

Disabling zoom locks out everyone who needs to magnify text. On modern browsers it buys nothing in return.

Fix Remove user-scalable=no and any maximum-scale below 2 from the viewport meta tag.

Low impact · weight 4

Speed

10 checks · 25% of the total

Core Web Vitals from Google's own Lighthouse run, plus the transport facts we measure ourselves: first byte, weight, compression, caching.

Lighthouse performance score

Google's own composite of loading, interactivity, and visual stability, measured on a simulated mid-range phone. This is the number PageSpeed Insights puts at the top of its report.

Fix Work the opportunities below in order: they are ranked by how much loading time each one gives back.

High impact · weight 10

Real visitors get a fast page

The Chrome UX Report measures Core Web Vitals on real Chrome users over the last 28 days, and it is the data Google's ranking uses, not the lab run. It exists only for pages, or whole sites, with enough traffic; when only the site has data, that is what this shows.

Fix Fix whichever of LCP, INP and CLS is outside the good band below.

High impact · weight 8

Largest Contentful Paint

LCP is the moment the biggest element in the viewport finishes rendering, which is when a visitor feels the page has arrived. Google's threshold for good is 2.5 seconds.

Fix Preload the hero image or font, serve images sized for their slot in a modern format, and remove render-blocking CSS.

High impact · weight 8

Cumulative Layout Shift

CLS measures content jumping around after it appears. It is why a visitor taps the wrong button when an ad or a late-loading image pushes the page down under their finger.

Fix Reserve space with width and height attributes on images and iframes, and never insert content above what is already on screen.

Medium impact · weight 6

Interaction to Next Paint

INP is how long the page takes to visibly respond after a tap or a click. Above 200ms the interface starts to feel like it is ignoring the user. INP replaced First Input Delay (FID) as a Core Web Vital in March 2024.

Fix Break up long JavaScript tasks, defer work that is not needed for the first interaction, and cut third-party scripts on the critical path.

Medium impact · weight 6

HTML is compressed in transit

Gzip or Brotli typically cuts HTML to a quarter of its size. Serving it uncompressed wastes that bandwidth on every single request, and it is usually one line of server config.

Fix Enable gzip or brotli for text responses in nginx, the CDN, or the application server.

Medium impact · weight 5

Time to first byte

How long the server took to start answering, measured on our own request. A slow first byte delays everything downstream no matter how well the front end is built.

Fix Cache the response, remove slow database queries from the request path, or move the origin closer to visitors with a CDN.

Medium impact · weight 5

Response carries cache headers

Without a usable Cache-Control header, browsers and CDNs re-fetch the page on every visit, so returning visitors pay the full load cost again for content that did not change.

Fix Send Cache-Control with a max-age suited to how often the page really changes, even if that is only a few minutes.

Low impact · weight 4

HTML document weight

This is the HTML document alone, before images and scripts. A multi-megabyte document usually means the whole page was rendered server-side into one response, and every visitor pays for it on every load.

Fix Paginate long lists, move repeated markup into components, and stop inlining large data blobs into the document.

Low impact · weight 4

Images are not oversized

Images are usually most of a page's bytes. A photo straight from a camera or a design tool can weigh several megabytes when a few hundred kilobytes would look the same on screen, and every visitor on a phone downloads all of it. We ask each of the page's first ten images for its size, without downloading it.

Fix Export photos at the size they are shown, in WebP or AVIF, at quality 75-85.

Low impact · weight 3