A 100 in Lighthouse’s SEO category takes about ten minutes on an Astro site. Ranking takes the other twenty-four items. This is the technical checklist we work through on every site we launch, in the order the items matter, with the Astro-specific way to do each one.
Work top to bottom. The first five are the ones that cost you traffic if they are wrong.
Foundation (Do These First)
1. Set the Site URL
Astro cannot build a correct absolute URL without knowing the origin. Set site in astro.config.mjs before anything else on this list:
export default defineConfig({
site: "https://yoursite.com",
});
Canonicals, the sitemap and Open Graph URLs all derive from it. Without it they come out relative, missing, or pointing at localhost.
2. Put a Canonical on Every Page
A canonical tells Google which version of a URL to index when several resolve to the same content (with and without www, with and without a trailing slash, with tracking parameters):
<link rel="canonical" href={new URL(Astro.url.pathname, Astro.site)} />
In Amrify layouts the canonical is generated from the site’s base_url and the page path, with a frontmatter override for the rare page that needs one.
3. Pick One Trailing-Slash Rule
/about and /about/ are two URLs to Google. Choose a rule in config and make every internal link match it:
// astro.config.mjs
export default defineConfig({
trailingSlash: "always", // or "never"
});
Then check the host: some CDNs redirect one form to the other on their own, and a redirect chain on every internal link is a slow crawl.
4. Write a robots.txt
Drop it in public/:
User-agent: *
Allow: /
Disallow: /api/
Sitemap: https://yoursite.com/sitemap-index.xml
Block what should never be indexed (API endpoints, internal tools, a staging path) and always point to the sitemap. Do not block CSS or JavaScript; Google needs them to render the page.
5. Generate an XML Sitemap
Use the official integration:
pnpm add @astrojs/sitemap
// astro.config.mjs
import sitemap from "@astrojs/sitemap";
export default defineConfig({
site: "https://yoursite.com",
integrations: [sitemap()],
});
It writes sitemap-index.xml at build time with every static route. Exclude thank-you pages and the 404 with the filter option, then submit the index URL in Search Console once the site is live.
Meta Tags
6. Unique Title Tags, Keyword First
One <title> per page, under about 60 characters, with the topic before the brand:
# Good: topic first
meta_title: "Astro SEO Checklist 2026 | 25 Steps | Amrify"
# Weak: brand first
meta_title: "Amrify | Astro SEO Checklist"
Google rewrites titles it considers poor. Give it no reason to.
7. Unique Meta Descriptions
Under 160 characters, specific to the page, written to earn the click:
description: "25 technical SEO steps for Astro sites, in priority order, with the Astro-specific way to do each one."
Descriptions are not a ranking factor. They are the sentence that decides whether a person clicks your result or the one below it.
8. Open Graph Tags
These control the preview when a page is shared in Slack, LinkedIn, iMessage or Facebook:
<meta property="og:title" content="Page Title" />
<meta property="og:description" content="Page description" />
<meta property="og:image" content="https://yoursite.com/images/og/page.png" />
<meta property="og:url" content="https://yoursite.com/page/" />
<meta property="og:type" content="website" />
Use a 1200x630 image. A unique image per page is better than one site-wide card, and Astro can generate one per page at build time from the page’s title; the Amrify layouts do this for every page.
9. X (Twitter) Card Tags
X still reads its own tag family:
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Page Title" />
<meta name="twitter:description" content="Page description" />
<meta name="twitter:image" content="https://yoursite.com/images/og/page.png" />
The old Card Validator is gone; check the preview by pasting the link into a draft post, or use any social preview tool.
10. Robots Meta Per Page
Set the default once in the layout and override where needed:
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
max-image-preview:large lets Google show a full-size image in results and Discover. Put noindex on thank-you pages, search results and anything you would be embarrassed to see ranking.
Structured Data
Three things changed since the last time most checklists were written: Google dropped the sitelinks search box, limited FAQ rich results to a narrow set of sites, and AI assistants started reading JSON-LD to describe businesses. The items below reflect that.
11. Organization or LocalBusiness Schema
The homepage needs an Organization (or a specific type such as RoofingContractor for a local trade) that names the business and links its profiles:
{
"@type": "Organization",
"name": "Your Business",
"url": "https://yoursite.com",
"logo": "https://yoursite.com/images/logo.svg",
"sameAs": ["https://www.linkedin.com/company/you", "https://github.com/you"]
}
This is the entity Google and the AI assistants attach everything else to. The free schema generator writes a LocalBusiness block from a form if you want a starting point.
12. WebSite Schema
{
"@type": "WebSite",
"url": "https://yoursite.com",
"name": "Your Site Name"
}
The name here is one of the signals Google uses for the site name shown in results. Skip the SearchAction you will see in older guides: Google no longer shows the sitelinks search box, so that markup does nothing now.
13. BreadcrumbList Schema
Breadcrumbs tell Google how the site is organized and can replace the raw URL in the result:
{
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://yoursite.com/" },
{ "@type": "ListItem", "position": 2, "name": "Blog", "item": "https://yoursite.com/blog/" },
{ "@type": "ListItem", "position": 3, "name": "This Post" }
]
}
Generate them from the pathname in the layout so they can never disagree with the actual URL.
14. Article Schema on Posts
{
"@type": "BlogPosting",
"headline": "Post Title",
"datePublished": "2026-03-10",
"dateModified": "2026-09-17",
"author": { "@type": "Person", "name": "Author Name" },
"description": "Post description"
}
Keep dateModified honest: bump it when the content changes, and only then. A fake freshness date is easy to spot from the page text.
15. FAQ Schema, With Adjusted Expectations
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Your question?",
"acceptedAnswer": { "@type": "Answer", "text": "Your answer." }
}
]
}
Most sites will no longer get an expandable FAQ rich result from this; Google restricted that to a small set of government and health sites. It is still worth emitting where the FAQs are real page content, because the same block gives AI answer engines clean question and answer pairs to quote. Do not stamp it on pages that have no FAQ.
Performance (Core Web Vitals)
16. Largest Contentful Paint Under 2.5 Seconds
On an Astro site LCP is almost always the hero image or the heading font:
- Give the hero image
loading="eager"andfetchpriority="high"; everything else stays lazy - Preload the one font file the heading uses
- Keep the small stylesheets Astro inlines by default (
build.inlineStylesheets: "auto") and do not add a render-blocking script above the fold
17. Zero Cumulative Layout Shift
Layout shift comes from things that arrive late and push content around:
- Explicit
widthandheighton every image, so the browser reserves the space - A font strategy that does not reflow text:
font-display: optionalavoids the swap entirely,swapneeds a fallback with matching metrics - Nothing injected above the fold after load, including cookie bars and announcement strips
18. Total Blocking Time Near Zero
Astro’s zero-JavaScript default does most of this for you:
- Hydrate islands with
client:visibleorclient:idlerather thanclient:loadunless the widget must be interactive on first paint - Keep islands small; a whole page as one island is a React app with extra steps
- Load analytics and chat widgets after the page is interactive, or not at all on pages that do not need them
19. Images
Every content image should be modern-format, right-sized and lazy:
---
import { Image } from "astro:assets";
import hero from "../assets/hero.jpg";
---
<Image src={hero} alt="Crew installing a standing-seam metal roof" layout="constrained" width={1200} height={800} />
With a layout set, Astro’s <Image> converts to WebP and writes the srcset and sizes attributes for you; images in public/ are served as-is and get none of this.
20. Fonts
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
If you load from Google Fonts, preconnect to both hosts and request only the weights you use. Self-hosting saves the extra connections at the cost of managing the files yourself; either is fine if the display strategy in item 17 is right.
Content and Internal Links
21. One H1, Then a Real Hierarchy
h1: Page Title
h2: Major Section
h3: Subsection
h2: Another Major Section
Exactly one <h1>, and no skipped levels. Headings are how both Google and screen readers outline the page.
22. Link Related Pages to Each Other
Three to five internal links per page, chosen because a reader would want them: a service page to its city pages, a post to the service it explains, a category page to every item in it. Orphan pages (linked from nowhere) are the pages Google forgets first.
23. Descriptive Anchor Text
<!-- Good -->
<a href="/blog/astro-seo-guide/">the Astro SEO checklist</a>
<!-- Useless -->
<a href="/blog/astro-seo-guide/">click here</a>
The anchor is Google’s label for the destination. Make it the page’s topic in plain words.
24. Alt Text That Describes the Picture
<!-- Good -->
<img alt="Lighthouse report showing 100 in all four categories" src="..." />
<!-- Keyword stuffing -->
<img alt="astro template astro lighthouse astro seo" src="..." />
Write what a person who cannot see the image needs to know. Image search follows from that; stuffing hurts both.
25. Short, Readable URLs
Good: /blog/astro-seo-checklist/
Bad: /blog/post-12345/
Bad: /blog/the-complete-and-ultimate-astro-seo-checklist-for-beginners-2026/
Three to five words that name the topic. No dates in blog URLs unless the content is genuinely dated, because you will not want to redirect them later.
After Launch
- Submit the sitemap in Google Search Console
- Request indexing for the homepage and the top service or product pages
- Validate structured data with Google’s Rich Results Test
- Check the Open Graph preview with Facebook’s Sharing Debugger
- Run Lighthouse on the key pages from a real network, not just localhost
- Watch Search Console for crawl errors, indexing drops and Core Web Vitals field data over the first month
What Amrify Handles for You
The shared layouts in the free Starter cover items 1 to 15 from site.json and the content schemas: canonicals, sitemap, robots, meta and social tags, the per-page social card, and Organization, WebSite, Service, Article, FAQPage and BreadcrumbList JSON-LD. Items 16 to 20 are enforced rather than promised, with a Lighthouse 100 gate in CI that fails a build when any category slips. Items 21 to 25 are yours, and the templates give you the heading structure, image components and clean routing to get them right. Amrify Pro adds the local SEO engine on top: service-area and service-in-city pages with a build gate that refuses thin content.
For the performance half in detail, read how the Lighthouse 100 gate works.
Check items 1 to 10 on a live site right now. The free Website Grader runs Google’s own PageSpeed test on a simulated phone and reports the title and description, structured data, HTTPS redirect, SSL expiry, robots.txt, XML sitemap and llms.txt in about thirty seconds, with no email and no account. Missing items 11 or 12? The schema generator writes them, and the llms.txt generator covers the file the grader checks last.