Frontend / Checklist
Technical SEO you can verify from the HTML
Technical SEO is the part of SEO you can confirm from the source: every rule here names the tag, header, file or property to check. The groups follow where the fix lives - the head block, the crawl surface, the link graph, the URL, the JSON-LD, the share card - with a priority that says what to fix first.
92 rules in 8 groups. Adapted from the Front-End Checklist (David Dias, MIT), rewritten and re-verified September 2026.
What this checklist decides
Whether a crawler can find a page, understand what it is, and trust the signals around it - before any judgement about the writing applies. Meta tags, canonicals, robots directives, sitemaps, redirects and structured data are mechanical: each one is right or wrong, and a build step can check most of them on every deploy.
Nothing here is Critical - a missing canonical does not break the site the way a missing charset does - but the High rows decide whether a page is indexed at all. The Content and Local groups at the end need a human rather than a script; they are here because the technical work is wasted when the page behind it is thin.
- Critical blocks the ship
- High fix before launch
- Medium fix this quarter
- Low worth it when cheap
Meta tags
Ten rules that live in the head block. They are the cheapest to check and the most often wrong, because a template sets them once and nobody reads the output again.
| Rule | Priority | What to do |
|---|---|---|
| Give every page a title of 30 to 60 characters | High | The <title> is the headline in the result. Put the page's subject first and the site name last, and keep it inside the 30 to 60 character window so it is neither truncated nor padded. Exactly one per page. |
| Write a 110 to 160 character description per page | High | <meta name="description"> is the snippet Google shows when it agrees with you. Write a sentence that states what the page delivers, 110 to 160 characters, written for this page and no other. |
| Keep every title unique across the site | High | Two pages with the same <title> compete with each other and get merged in reports. Derive titles from the page's own subject, never from the template alone, and diff the full list in the build. |
| Keep every meta tag inside head | High | A <meta> or <link rel="canonical"> that lands after the first byte of the body - usually because a stray element closed the head early - is ignored. Validate that the head holds only head elements and nothing is emitted before it. |
| Set the robots meta once and correctly | High | Use <meta name="robots" content="index, follow"> or omit the tag - that is the default. Reserve noindex for pages you truly want out of the index, and never pair it with a canonical to another URL on the same page. |
| Sync the canonical tag with any Link header | Medium | If the server or CDN emits Link: <url>; rel="canonical", it must name the same URL as the <link rel="canonical"> in the HTML. Two answers means Google picks. On a static host that sends no such header, the HTML tag is the only source and this is a non-issue. |
| Never repeat a meta description | Medium | Duplicated descriptions mark a page as templated. Diff them at build time the way you diff titles and rewrite any collision. Generated pages - tags, pagination, filters - need a formula that includes the part that varies. |
| Ship a favicon on every page | Medium | <link rel="icon" href="/favicon.ico" sizes="32x32"> plus an SVG or PNG variant. Google shows it next to mobile results, and a 404 on /favicon.ico is a wasted request on every visit. |
| One H1 that says what the page is | Medium | Exactly one <h1>, worded differently from the title, holding the page subject in plain words. Several H1s are legal HTML but dilute the signal and confuse outline tools and screen readers alike. |
| Keep nosnippet off pages that should rank | Medium | nosnippet in the robots meta and the data-nosnippet attribute strip the description from results and the page from AI overviews. Use them for legal or sensitive copy only, never on a landing page. |
A head block that passes every row above, for a page at /pricing/. Title 32 characters, description 134, one canonical, one robots directive, the share card complete:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pricing - Acme Uptime Monitoring</title>
<meta name="description" content="Three plans, one price per monitor, no seat fees. Compare the Free, Team and Business tiers and start a trial without entering a card.">
<link rel="canonical" href="https://example.com/pricing/">
<meta name="robots" content="index, follow, max-image-preview:large">
<meta property="og:type" content="website">
<meta property="og:title" content="Pricing - Acme Uptime Monitoring">
<meta property="og:description" content="Three plans, one price per monitor, no seat fees.">
<meta property="og:url" content="https://example.com/pricing/">
<meta property="og:image" content="https://example.com/img/og-pricing.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Acme pricing: Free, Team and Business tiers side by side">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://example.com/img/og-pricing.png">
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
</head> Gotcha: the 30 to 60 and 110 to 160 windows are a proxy for pixels, not a rule about characters. A title full of wide capitals truncates earlier than one in lowercase, and Google rewrites titles it considers weak regardless of length. Check the edge cases in a SERP preview rather than trusting the count alone.
Indexing and crawling
Whether the crawler is allowed in, what it is told to keep, and whether the sitemap tells the truth. A sitemap lists only canonical, indexable URLs that return 200, in the directory form the site actually serves.
| Rule | Priority | What to do |
|---|---|---|
| Put a self-referencing canonical on every page | High | <link rel="canonical" href="https://example.com/path/"> on every indexable page: an absolute URL, one per page, naming the version you want indexed - itself in most cases. Query-string and parameter variants point at the clean URL. |
| Publish a robots.txt at the site root | High | /robots.txt served with 200 and text/plain, allowing crawlers by default, disallowing only what should never be crawled, ending with a Sitemap: line. A 5xx on robots.txt makes Google pause crawling the whole site. |
| Generate an XML sitemap and submit it | High | Build /sitemap.xml from the page registry on every deploy, reference it in robots.txt, and submit it once in Search Console and Bing Webmaster Tools. A hand-maintained sitemap goes stale within a month. |
| Keep the sitemap valid | High | Well-formed XML in the sitemaps.org namespace, absolute URLs, at most 50,000 entries and 50 MB uncompressed per file - use a sitemap index beyond that. Validate it in CI; Search Console reports a parse error days after the deploy. |
| No noindex URLs in the sitemap | High | A sitemap entry says "index this"; a noindex meta or header on the same URL says the opposite. Filter the sitemap through the same registry field that sets the robots directive so the two cannot disagree. |
| No 4xx or 5xx URLs in the sitemap | High | Every <loc> returns 200. Crawl the sitemap after each deploy and fail on anything else; a deleted page that lingers in the file wastes crawl budget and reports as an error until someone notices. |
| Never send conflicting robots directives | High | The robots meta tag, the X-Robots-Tag header and robots.txt must agree. A page blocked in robots.txt can never be seen to carry noindex, so it can still be indexed from links alone; unblock it and use noindex instead. |
| Audit every noindex page on purpose | Medium | List every URL that carries noindex and confirm each one was meant. Frameworks and CMS plugins add it to previews, staging and pagination silently; a noindex that leaks onto a money page is the classic invisible outage. |
| Point canonicals at 200 URLs, not redirects | Medium | A canonical that 301s somewhere else forces Google through a chain before it trusts the signal. Every rel="canonical" target must respond 200 in its final form - scheme, host and trailing slash included. |
| Do not mix indexability signals | Medium | noindex plus a canonical to another page, noindex plus a sitemap entry, a canonical plus a redirect on the same URL - each pair sends two answers. Pick one signal and remove the other. |
| Make every page you want ranked indexable | Medium | Confirm the important pages return 200, carry no noindex, are not blocked in robots.txt, sit behind no login or JavaScript-only render, and appear in the sitemap. URL inspection in Search Console shows exactly what Google saw. |
| Canonicalize paginated pages to themselves | Medium | Page 2 of a list is its own URL: the canonical on /blog/page/2/ points at /blog/page/2/, not at page 1. Pointing every page at the first one hides everything past it from the index. |
| List every indexable page in the sitemap | Medium | The sitemap is the crawl list. A page missing from it can still be found through links but is crawled later and less often. Generate it from the same page registry the router uses so nothing depends on someone remembering. |
| Keep sitemap URLs on the live domain | Medium | Every <loc> uses the canonical host and scheme - the www form or the apex, never both, never a preview host. A build that runs on a preview deployment must not bake the preview origin into the file. |
A robots.txt that does its whole job in five lines - open by default, closed where it should be, and pointing at the sitemap:
User-agent: *
Disallow: /drafts/
Disallow: /api/
Sitemap: https://example.com/sitemap.xml Gotcha: Disallow controls crawling, not indexing. A URL blocked in robots.txt can still show up in results with no snippet when other sites link to it. To keep a page out of the index, let Google fetch it and serve noindex; to keep it out of both, do both, in that order.
Links and redirects
Links are how the crawler discovers pages and how authority moves between them; every broken, redirected or nofollowed one is a leak you control.
| Rule | Priority | What to do |
|---|---|---|
| Fix every broken internal link | High | Crawl the built site and fail the build on any internal href or src that does not return 200. Internal links are entirely under your control, so a 404 from one is a bug, not a condition to monitor. |
| Redirect in one hop | High | Every old URL 301s straight to its final destination. http to https to www to trailing slash is three hops that cost time on every request and shed signal at each step. Collapse the chain when you add the new rule. |
| Fix or drop broken and malformed external links | Medium | Check outbound links on a schedule. A target that 404s, that redirects to a parked domain, or whose href is malformed - a space, a missing scheme, a javascript: value - gets fixed, replaced with an archive copy, or removed. |
| Give dead-end pages a way onward | Medium | A page with no outgoing links traps the crawler and the reader. Every page links to at least its parent or hub, one related page, and the next step you want taken. |
| Link to orphan pages from somewhere | Medium | A URL in the sitemap that no other page links to is an orphan: reached late, ranked as unimportant. Diff the sitemap against the crawl graph in CI and add a link from a hub or a related page for every hit. |
| Strengthen pages with a single inbound link | Medium | One link from a footer or a tag page is a weak signal. Pages you care about need several contextual links from related content - inside paragraphs, with descriptive anchor text - not only from navigation. |
| Link to key pages from your strongest pages | Medium | The home page, the hubs and the posts that already rank should link to the pages that convert. Internal links move authority; spend it deliberately rather than letting the template decide. |
| Link out to real sources | Medium | Cite the spec, the docs or the primary source with an ordinary followed link. Refusing to link out does not hoard authority; it only makes the page less useful and less credible. |
| Do not nofollow internal links | Medium | rel="nofollow" on an internal link throws away the authority you are trying to pass and saves no crawl budget. Put noindex on the target if it should not rank, and leave the link alone. |
| Link to the final URL, not through a redirect | Medium | Update internal links whenever you add a redirect, so the crawler never has to follow one from your own pages. Redirects exist for inbound links on other sites that you cannot change. |
| Never link from HTTPS to HTTP | Medium | An http:// URL on an HTTPS page is mixed content for a resource and a downgraded referral for a link. Rewrite it to https://, or drop the link if the target still does not support it. |
| Use the tel: and mail link schemes correctly | Medium | Phone numbers link with tel: and the full international number with no spaces; email addresses use the mail link scheme, not a contact form disguised as a link. Neither is crawlable, so neither belongs in the sitemap or a link check. |
Gotcha: a link checker that only reads status codes misses soft 404s - a page that returns 200 with "not found" in the body, which is how many CMS and hosting error pages behave. Check for a known string or the expected title on the destination, not only the code.
URLs and documents
URL shape and document hygiene are mostly one-time decisions that get expensive to change once a page ranks, so make them before launch and enforce them in the build.
| Rule | Priority | What to do |
|---|---|---|
| Separate words with hyphens | Medium | /core-web-vitals/, never /core_web_vitals/ or /corewebvitals/. Google reads a hyphen as a word separator and an underscore as a joiner, and readers can parse the hyphenated one at a glance. |
| Keep URLs lowercase | Medium | Paths are case-sensitive on most hosts, so /About/ and /about/ are two URLs with the same content. Emit lowercase everywhere and 301 any uppercase request to the lowercase form. |
| Keep URLs short | Medium | Three to five words in the slug, no dates unless the date is the point, and no category chains deeper than the site structure needs. Long URLs get truncated in results and copied wrong in messages. |
| Put the page's subject in the slug | Medium | The slug reads like the title in a few words: /pricing/, /guides/css-grid/. A numeric ID or a hash in the slug tells the reader and the ranker nothing. |
| Keep special characters out of URLs | Medium | Letters, digits, hyphens and slashes only. Spaces (which become %20), apostrophes, ampersands and non-ASCII characters get encoded differently by different tools and turn one page into several URLs. |
| Use trailing slashes consistently | Medium | Pick /path/ or /path for the whole site and 301 the other form. Both answering 200 is duplicate content; the directory form is the natural fit for a static host that serves one folder per page. |
| Strip URL parameters that change nothing | Medium | Tracking, sort and session parameters create endless duplicate URLs. Move state that matters into the path, canonicalize every parameter variant to the clean URL, and keep campaign parameters out of internal links. |
| Keep HTML under the crawl size limit | Medium | Googlebot reads the first 15 MB of an HTML file and stops. A giant inlined data blob, an SVG sprite the size of a font, or a page listing thousands of rows can push the real content past that line. Paginate the data and keep the document lean. |
| Fix malformed HTML | Medium | Unclosed elements, body-only elements inside the head, and a second document pasted in from a widget can end the head early or hide content from the parser. Run the built pages through a validator in CI and fail on errors. |
| Serve every file with the right MIME type | Medium | HTML as text/html, the sitemap as application/xml, SVG as image/svg+xml. A sitemap served as text/plain is not parsed. Check the response headers, not the file extension. |
| Keep linked PDFs under 60 MB | Medium | Google indexes PDF text but gives up on very large files. Keep each linked PDF under 60 MB, give it a descriptive filename, and link to it from an HTML page that summarizes what is inside so the page ranks instead of the file. |
| Publish llms.txt on documentation-heavy sites | Medium | An emerging convention: /llms.txt lists your key pages with a one-line summary each, in Markdown, so language-model crawlers find the canonical page for each topic. Generate it from the page registry alongside the sitemap. |
| Leave stop words out of slugs when they add nothing | Low | /guide-to-the-css-grid/ says no more than /css-grid-guide/. Drop articles and prepositions unless removing one changes the meaning - and never rename a ranking URL just for this. |
Gotcha: renaming URLs to fix a slug is only worth it when the redirect ships in the same deploy. A prettier slug on a page that has ranked for years under the old one costs more than it earns if the 301 lands late, and it never earns much even when it lands on time.
Structured data
JSON-LD is the one place you get to tell the ranker what a page is in its own vocabulary. Every block must be valid, true, and on a page that is meant to be indexed.
| Rule | Priority | What to do |
|---|---|---|
| Mark up articles with a valid Article block | High | Every post carries an Article (or TechArticle, BlogPosting, NewsArticle) with headline, image, datePublished, dateModified, author and publisher. The headline matches the visible title and the dates match what the page shows. |
| Give every page type a JSON-LD block | High | One <script type="application/ld+json"> per page, chosen by page type: WebPage or CollectionPage for hubs, Article for posts, Product for products. Generate it from the page registry, never by hand. |
| Never put schema on a noindex page | High | Structured data on a page carrying noindex is a contradiction Search Console flags. Either the page is meant to be indexed - drop the noindex - or it is not, in which case strip the JSON-LD from it. |
| Validate every JSON-LD block in CI | Medium | Malformed JSON, a missing @context, a property the type does not define, or a required field left empty makes the block invisible. Parse every block at build time and run one page per type through the Rich Results Test before release. |
| Add BreadcrumbList to nested pages | Medium | A BreadcrumbList of ordered ListItem entries - position, name, item - mirroring the visible trail. First item is the home page, last is the current page. |
| Mark up real FAQs with FAQPage | Medium | Use FAQPage only for a visible question-and-answer list, with Question and acceptedAnswer pairs that match the text on the page. Google has limited FAQ rich results to government and health sites since 2023, so expect no visual reward - the value is in machine readability. |
| Declare the Organization | Medium | Organization with name, url and logo, plus a sameAs array listing only profiles that exist, on the home page or every page. It feeds the knowledge panel and the publisher block in every Article. |
| Mark up products with price and availability | Medium | Product with name, image, description and an offers block carrying price, priceCurrency and availability. Add aggregateRating only when real reviews exist; an invented rating earns a manual action. |
| Use Review markup only for real reviews | Medium | Review on the item being reviewed, with the reviewer named and the rating scale stated. Never review your own organization, and never mark up testimonials as reviews - both are named in Google's spam policies. |
| Mark up embedded videos with VideoObject | Medium | VideoObject with name, description, thumbnailUrl, uploadDate, duration and contentUrl or embedUrl. Without it a video page is invisible to video search and key moments. |
| Mark up authors as people with a URL | Medium | The Article author is a Person (or Organization) with name and a url to an author page that lists credentials and other work. A bare string name earns nothing. |
| Skip the WebSite search block | Low | Google retired the Sitelinks Searchbox in 2024, so the WebSite block with a potential search action earns nothing, and a broken one - a query URL that 404s or ignores its parameter - is a standing Search Console error. Delete it; a plain WebSite block with name and url is all that is worth keeping. |
The smallest Article block that passes the Rich Results Test and satisfies the author and publisher rows - every value below also appears somewhere visible on the page:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "How to read a flame graph",
"description": "What the width, the stacking and the colors mean, and the three shapes that point at a fix.",
"image": "https://example.com/img/flame-graph.png",
"datePublished": "2026-09-01",
"dateModified": "2026-09-19T23:30:16.567Z",
"author": {
"@type": "Person",
"name": "Jane Example",
"url": "https://example.com/authors/jane/"
},
"publisher": {
"@type": "Organization",
"name": "Example Engineering",
"logo": { "@type": "ImageObject", "url": "https://example.com/img/logo.png" }
},
"mainEntityOfPage": "https://example.com/blog/flame-graphs/"
}
</script> Gotcha: passing the Rich Results Test proves the block parses, not that it is honest. A rating with no reviews behind it, an FAQ that is not on the page, a Product with no visible price - all validate cleanly and all qualify for a manual action. Markup describes the page; it does not improve it.
Social sharing
Open Graph is what chat apps and social feeds read to build the preview card. It is not a ranking signal, but it decides whether a shared link gets clicked.
| Rule | Priority | What to do |
|---|---|---|
| Add the Open Graph set to every page | Medium | og:title, og:description, og:type, og:url, og:image and og:image:alt on every page. Slack, Discord, iMessage, WhatsApp and every social feed read these to build the card; the title may be shorter than the page title. |
| Make og:url match the canonical | Medium | og:url and <link rel="canonical"> name the same absolute URL. A mismatch splits share counts and lets a platform cache a parameterized URL as the page. |
| Ship a 1200 by 630 og:image | Medium | A 1200 by 630 pixel PNG at an absolute URL, with og:image:width and og:image:height declared so the first share renders without a second fetch. Keep text away from the edges; platforms crop to 1.91:1 or to a square. |
| Add twitter:card and twitter:image only | Medium | <meta name="twitter:card" content="summary_large_image"> and twitter:image are read by several platforms, not only X. The site and creator handle tags are valid only when the account actually exists; an invented handle is a broken attribution on every share. |
| Add share links only where people share | Low | Plain links to each platform's share URL on articles, plus a copy-link button - no third-party share widget, which adds a script, a tracker and a layout shift. Skip them on pages nobody shares. |
| Link only to social profiles you keep active | Low | A footer icon that leads to an abandoned account is a trust hit. Link the profiles you post on, and list exactly those in the Organization sameAs - the two lists should match. |
Gotcha: platforms cache the preview card hard. After changing og:image or og:title, run the URL through each platform's link debugger to refresh it, or the old card lingers for weeks on every re-share.
Content and E-E-A-T
The rows a build step cannot check. They translate what Google's quality rater guidelines call experience, expertise, authoritativeness and trust into things a page can actually show.
| Rule | Priority | What to do |
|---|---|---|
| Answer the query better than the current top result | High | Before writing, open the pages that rank for the query and decide what they miss. Original data, hands-on testing, a clear verdict or a worked example earns the click; rewording the top result does not. |
| Identify your YMYL pages and hold them to a higher bar | High | Pages touching money, health, law or safety are judged on expertise and accuracy first. Name a qualified author, cite primary sources, date the last review, and add a disclaimer wherever the advice could cause harm. |
| Publish an About page that says who runs the site | Medium | A real About page naming the people, the purpose and the history, linked from the footer. Raters and readers look for it; a site with no one behind it reads as anonymous, and anonymous loses on any contested query. |
| Disclose affiliate relationships plainly | Medium | State near the top of any page with affiliate links that you may earn a commission, and add rel="sponsored" to those links. A disclosure buried in the footer satisfies neither Google nor the FTC. |
| Edit AI-drafted content before it ships | Medium | Verify every fact, cut the filler, add what you know from experience, and take responsibility with a byline. Unedited generated text is exactly what the helpful-content signals were built to demote. |
| Write anchor text that names the destination | Medium | The link text says what the target page is about - "the CSS Grid cheatsheet", not "click here" or "this". It informs the ranker and the screen-reader user in the same move. |
| Keep link density reasonable | Medium | Link where a reader would want to go next, not on every noun. A paragraph that is mostly links reads as a directory, and every extra link dilutes the ones that matter. |
| Show a byline on every article | Medium | A visible author name near the title, linked to an author page, matching the author in the JSON-LD. Anonymous articles struggle on any topic where accuracy matters. |
| Show the author's credentials | Medium | The author page states what qualifies this person to write on the topic - role, years in it, prior work - and links to profiles that confirm it. Credentials belong where the reader can check them, not in a hidden bio field. |
| Cite primary sources with links | Medium | Link the spec, the study, the documentation or the announcement you rely on, at the sentence that relies on it. Claims with a source read as researched; claims without one read as guessed. |
| Publish a Contact page with a method that reaches a person | Medium | A Contact page linked from the footer with at least one channel someone actually reads and a stated response expectation. A form that lands in an unwatched inbox fails the purpose and the rater. |
| Show published and updated dates | Medium | Visible dates on articles that match datePublished and dateModified in the JSON-LD, with the modified date moving only when the content changes. Bumping it without an edit is a pattern Google detects and discounts. |
| Add disclaimers where advice carries risk | Medium | Medical, financial, legal and safety content states its limits and who to consult instead. A short line near the top and a fuller statement at the end, written in plain words rather than legal boilerplate. |
| Publish an editorial policy | Medium | One page describing how content is researched, reviewed, corrected and updated, and how sponsors and affiliates are handled. It is what a careful reader opens when deciding whether to trust a claim. |
| Show freshness only where it is real | Medium | Verified-on dates, version numbers and changelogs on pages that describe moving targets. A current year in the title over content that has not moved in years is the opposite signal, and readers notice before Google does. |
| Write for the reader, not the keyword count | Medium | Use the term naturally where it belongs, then use synonyms and plain description. Repeating the target phrase in every paragraph is detectable, hurts readability, and has not worked for a decade. |
| Structure content so machines can extract it | Medium | Headings that state the topic, a direct answer in the first paragraph under each one, lists and tables for comparisons, a definition where a term is introduced. Answer boxes and language-model crawlers quote the pages that are easy to lift from. |
| Write at a clear reading level | Medium | Short sentences, one idea each, concrete words over abstract ones. Technical readers skim; a page that takes two reads to understand loses to the one that takes one. |
| Put trust signals on the pages that convert | Medium | Testimonials with real names, a security badge that links to the audit, a physical address, a phone number, a privacy policy link. On pricing and checkout pages these move the decision more than any copy change. |
| Expand, merge or noindex thin pages | Medium | A page that says less than a search snippet - a tag page with two entries, a placeholder category, a hundred-word "guide" - is expanded, merged into a stronger page with a 301, or set to noindex. Leaving it drags the site's average down. |
Gotcha: E-E-A-T is not a ranking factor you can add with markup. The author schema, the About page and the bylines only count when a human can confirm them, so every signal on this table has to be true before it is marked up - the markup is the last step, not the first.
Local SEO
Only for businesses with a physical location or a service area. A SaaS or a content site skips this group entirely.
| Rule | Priority | What to do |
|---|---|---|
| Add LocalBusiness schema to the location page | Medium | LocalBusiness (or a subtype such as Restaurant or Dentist) with name, a PostalAddress, telephone, openingHoursSpecification, geo and url. One block per location page for a multi-location business. |
| Keep name, address and phone identical everywhere | Medium | The same business name, the same address format and the same phone number on the site, in the schema, on the Google Business Profile and in every directory. A suite number written two ways counts as a mismatch. |
| Show the physical address as text | Medium | The full street address in the footer or on the Contact page, in text inside an <address> element, never in an image. Google cross-checks it against the Business Profile listing. |
| Add geo meta tags where a local tool reads them | Medium | geo.region, geo.placename, geo.position and ICBM meta tags are ignored by Google but read by some directories and local tools. Cheap to emit when the schema already carries the coordinates. |
| Build one real page per service area | Medium | A distinct page per city or region you serve, each with its own content, contact details and schema - not one page listing every town, and not a hundred pages that differ only by the city name in the template. |
Gotcha: a location page holding an address in an image, a map embed and nothing else is thin content with schema on it. Write about the location - the services offered there, the team, parking, hours - or fold it into the main Contact page.
Keep going
The rows that block a deploy, from this page and its siblings, are collected on the launch checklist. Meta tags depend on the document being right first - the HTML checklist covers charset, viewport and lang - and the images checklist handles the share image and the LCP hero that technical SEO reports on.
Core Web Vitals are a ranking input too; the performance checklist has the thresholds and the fixes. Building a content site? The static site stack gets most of this page right by default, Next.js vs Astro settles the framework, and the CMS picks decide where the content lives.