Site checklist
For a static content site (blog, documentation). Stack-independent — HTML tags, files at the site root, HTTP headers, DNS records. Roughly ordered by effort/return.
Three files cover every client. Generate all of them from one SVG source at build time.
favicon.ico, multi-size 16/32/48. Not a bare 16×16 — Google renders at 32px minimum and wants a source above 48px, so a 16px original ships upscaled.icon.svg, with an explicittype="image/svg+xml". Put aprefers-color-schememedia query in the SVG’s own<style>block and one file covers light and dark tabs — no JS, no extra request. Safari ignores it; the light version shows.apple-touch-icon.pngat 180×180. iOS ignoresrel="icon"and bookmarks a screenshot of the page without it. Crop to the alpha bounding box and centre on white — iOS composites transparency onto black.
<link rel="icon" href="/favicon.ico" sizes="32x32"><link rel="icon" href="/icon.svg" type="image/svg+xml"><link rel="apple-touch-icon" href="/apple-touch-icon.png"><!-- PWA only: --><link rel="manifest" href="/manifest.webmanifest">- Google reads BMP, GIF, ICO, PNG, JPEG, PPM, TIFF — not SVG. Verify with
google.com/s2/favicons?domain=<host>; blank means no raster. - Keep icon URLs stable. Expect days to weeks before a change shows in results.
- 192/512 PNGs +
manifest.webmanifestbuy an Android home-screen install. Skip unless you want that.
Sources: Google Search Central ↗ · RealFaviconGenerator ↗
- Emit the full Open Graph + Twitter tag set:
og:type=articlewitharticle:published_time,twitter:card=summary_large_image, andog:image:alt. - Share image 1200×630 (1.91:1). Declare
og:image:width/heightso the crawler doesn’t have to fetch it to lay out the card. - Generate a share image per page from that page’s own title and description.
- Budget the text separately: ~160 chars for
meta description, ~220 for the card. Whole sentences, never a cut phrase. - Debug with opengraph.dev ↗.
title≤ 60 chars,description≤ 160 — where Google truncates.- Derive a description per page (subtitle, else first prose paragraph). Never let pages inherit one site-wide blurb.
- JSON-LD:
WebSite+Organizationon the home page;BreadcrumbList+ an article type elsewhere, withauthor,datePublished,dateModified, andimageequal toog:image. Don’t assert more than the visible page says.Articlefor a blog post,BlogPostingfor a dated one,TechArticlefor documentation — the same fields, a more specific claim.- Other types worth knowing for a content site:
FAQPage,QAPage,HowTo,SoftwareApplication,Dataset,AboutPage,Person. - Full vocabulary: schema.org ↗. What Google actually does something with is a much shorter list — search gallery ↗. Validate with the Rich Results Test ↗ (Google’s view) and the Schema Markup Validator ↗ (everything else).
<meta name="robots" content="max-image-preview:large">. Without it Google shows thumbnail-size images only, and the page cannot appear in Discover at all. One tag, site-wide. UseX-Robots-Tagfor non-HTML files.- Sitemap
lastmodfrom the content’s real change date (git commit date), not the build date. Google retired the sitemap ping endpoint in 2023 —lastmodis the ping now. - Check links in CI (
lycheeor equivalent), internal and external. Link rot is the default state of a site nobody checks. - One
<h1>, semantic<main>/<article>/<nav>, no JS-gated content, stable anchors. - Audit the built HTML in CI: titles, descriptions, canonicals, heading order, image alt and dimensions, structured data, sitemap-vs-shipped-files, internal links, crawl directives. Lighthouse gives a score, not a diff.
- Cite sources and include statistics; don’t keyword-stuff. Princeton GEO study (KDD 2024): +40%, +37%, −10% respectively.
AI readiness
Section titled “AI readiness”Ship a Markdown twin of every page. Generate it from the Markdown source if you have one.
Ways to serve it, strongest first:
- Content negotiation at the page URL — an edge function that returns Markdown when
Accept:asks for it. An agent hitting the normal URL gets Markdown with no second request and nothing to discover. SetVary: Accept, and check your CDN actually honours it — some vary only onAccept-Encoding, which lets a cached Markdown response reach a browser. - A guessable flat URL —
/<slug>.md. Costs nothing and an agent can try it blind. <link rel="alternate" type="text/markdown">and an HTTPLink:header. Correct, but fallbacks — assume most agents don’t look.llms.txt+llms-full.txt. Widely talked about, weakest of the four in practice: a whole extra document an agent has to know to fetch. If you ship one, curate it — 10–50 links in sections, not an exhaustive dump of every URL.
Serve every twin as Content-Type: text/markdown; charset=utf-8 — RFC 7763 makes the
charset parameter mandatory.
robots.txt: allow everything, name the AI agents explicitly (OAI-SearchBot, PerplexityBot, Claude-SearchBot, Google-Extended, GPTBot, ClaudeBot), and add Content-Signal ↗search=yes, ai-input=yes, ai-train=yes. Content-Signal is a group directive and a crawler obeys exactly one group — repeat it in every group, don’t declare it once under*.
Performance
Section titled “Performance”Hand the URL to any agent with Lighthouse and let it work the report to green. The fixes are mechanical: image formats and dimensions, render-blocking CSS/JS, font loading, cache headers.
Images it will flag, but not tell you the whole fix:
- Explicit
width/heightattributes on every image, plusimg { width: 100%; height: auto; }in CSS. The attributes give the browser an aspect ratio to reserve space with;height: autois what lets it scale responsively instead of stretching to the literal attribute height. Both halves or neither —width: 100%alone distorts. loading="lazy"below the fold,decoding="async".
Two it won’t suggest at all:
- Speculation Rules — a
<script type="speculationrules">block that prerenders the next page on hover or when a link enters the viewport. Navigation becomes instant. Chromium only, degrades to nothing elsewhere, and a static site can afford to be eager. - LQIP for anything above the fold — a placeholder small enough to inline in the
HTML, so there is something in the box before the real image lands. Doesn’t help the
metrics that
width/heightalready fix; it fixes what the wait looks like. ThumbHash ↗ (~25 bytes, handles alpha and aspect ratio) over blurhash ↗, or lqip-modern ↗ for a tiny inline WebP. See hash algorithms.
The one thing you can’t retrofit.
- Pick one canonical host and one trailing-slash policy. 301 every other form to it.
<link rel="canonical">on every page, absolute.- Never break a URL. Keep a redirect map in the repo and only ever append to it.
- The 404 page must return status 404. A catch-all serving 200 makes every typo an indexable duplicate.
- Slugs from the title, not the date or an id. No query params on content pages.
Feeds and search
Section titled “Feeds and search”- RSS/Atom, full content, declared with
<link rel="alternate" type="application/rss+xml">. - On-site search: Pagefind ↗ — static chunked index built at build time, no server or SaaS.
Security headers
Section titled “Security headers”Set at the host (_headers or equivalent):
Strict-Transport-Security,X-Content-Type-Options: nosniff,Referrer-Policy,Permissions-Policy, and aContent-Security-Policyincludingframe-ancestors. With no inline scripts a static site can run a strict CSP.Cache-Control: immutableonly for content-hashed filenames. Everything else keeps ETag revalidation./.well-known/security.txt(RFC 9116).- Grade at securityheaders.com ↗ or Mozilla Observatory ↗.
- SPF, DKIM, DMARC even if the domain never sends mail — otherwise anyone can spoof
it. Sending nothing:
-allSPF,p=rejectDMARC, and a null MX (RFC 7505). - CAA record to pin which CAs may issue for the domain.
- TLS itself needs no work — Let’s Encrypt is automatic on Netlify, Cloudflare, Vercel, GitHub Pages.
Accessibility
Section titled “Accessibility”Same structure crawlers and LLMs extract.
<html lang>, real alt text, no skipped heading levels, visible focus states, contrast, a skip link,prefers-reduced-motion.- Run
axeorpa11yin CI.
Registration and indexing
Section titled “Registration and indexing”- Google Search Console: verify by DNS TXT at the apex, and register the domain property, not the URL-prefix one. Covers every subdomain and survives a host migration.
- Submit the sitemap. That is the whole of discovery.
- Register with Bing Webmaster Tools too.
- IndexNow ↗ on deploy: one ping reaches Bing, Naver,
Seznam, Yep. Google does not support it — for Google, sitemap
lastmodis the signal. - Submit the site to the Wayback Machine once.
Cross-posting
Section titled “Cross-posting”Worth doing for distribution, not for SEO. rel=canonical is a hint, not a
directive, and pointing one from a high-authority platform at a low-authority personal
site is the case Google is most likely to ignore — the platform’s copy can end up as the
indexed one. It passes no link equity either; platform outbound links are nofollow.
Without a canonical it is strictly worse: two copies compete and the stronger domain wins.
- Publish on your own site first and confirm it is indexed, then cross-post. The order matters more than the tag.
- dev.to:
canonical_urlin frontmatter or API. Hashnode: canonical field in the editor and API. Medium: only via Import Story, which sets it automatically — writing in the editor directly does not. - Use the absolute URL in your chosen host and trailing-slash form.
- Posting a link to HN / lobste.rs / Reddit reaches the same audience with no duplicate at all.
Identity and licence
Section titled “Identity and licence”Google’s E-E-A-T ranks who is saying this. An unattributed page is the weakest form.
- Ship an about/author page.
- Point every page’s JSON-LD
authorat that page, so authorship resolves somewhere instead of dead-ending at a name. EmitAboutPage+Personon it, withsameAs. rel="me"links to your GitHub/Mastodon profiles.- Bluesky: a
_atprotoDNS TXT record makes your domain your handle — the platform’s actual verification mechanism, and free. <meta name="fediverse:creator" content="@you@instance">(Mastodon 4.3+) for an author byline under link previews. Allow the domain under Preferences → Public profile → Verification.- State a content licence explicitly.
Analytics
Section titled “Analytics”- Pick a cookieless analytics tool (Plausible, Umami, Cloudflare Web Analytics, GoatCounter). No client-side storage means no consent banner, which is both less code and less friction than the alternative.
- Know the retention before you pick: Cloudflare keeps unsampled data ~7 days then aggregates; some tools keep months but expose no API without a paid plan.
- Search Console only compares two periods and exports the current one, and drops data after 16 months. If the numbers matter, snapshot them into the repo on a schedule.
- Domain auto-renew on, registrar lock on. An expired domain loses the URLs, the backlinks, the email, and the search history at once. It is the only failure on this page you cannot undo.
- Uptime check on the home page.
- Keep the build reproducible and the content in git. A static site’s real backup is the repo, not the host.
If the domain actually sends mail (newsletter, transactional), you get a logo in the inbox via BIMI — the email counterpart to a favicon.
Prerequisites, in order:
- SPF and DKIM passing, aligned.
- DMARC at enforcement —
p=quarantineorp=reject.p=nonedoes not qualify. - Logo as SVG Tiny PS, square, under 32 KB, served over HTTPS.
- A BIMI DNS TXT record at
default._bimi.<domain>pointing at the SVG (and the certificate, if any).
The certificate is the expensive part, and it decides where the logo shows:
- VMC (Verified Mark Certificate) — needs a registered trademark. Gets the logo plus the blue checkmark in Gmail, and rendering in Apple Mail (iOS 16 / macOS 13+).
- CMC (Common Mark Certificate) — no trademark, but the logo must have been in continuous public use on the domain for 12+ months (the CA checks web archives). Logo only, no checkmark, Gmail and Yahoo only.
- Either runs roughly $750–1750/yr plus trademark costs, 2–4 weeks to validate. Sectigo is the main CA now — Entrust exited and sold to them.
Worth it for a brand sending volume; not for a personal blog’s newsletter. Steps 1–2 are worth doing regardless. Apple Business Connect is a separate, certificate-free path to an Apple Mail logo.