Because speed is useless if search engines can’t find you.
TL;DR
Astro SEO checklist ranked by impact:
- Canonical URLs on every indexable page
- Unique title tags and meta descriptions
- Article/Product/HowTo JSON-LD where relevant
- Open Graph and Twitter/X cards
- One clear H1 and logical heading hierarchy
- Sitemap + robots.txt
- Image optimization with Astro
/
- Internal links with descriptive anchors
- Breadcrumbs + BreadcrumbList schema
- Redirect strategy for changed slugs
- dateModified for updated content
- Pagination rules: canonical, rel="prev" / rel="next", and noindex where needed
- llms.txt / AI crawler policy
- Site search for content-heavy sites
- Content collection schemas / Zod validation
1. Why SEO Still Matters (Even for Lightning-Fast Sites)
Speed doesn’t guarantee visibility. Search engines don’t just reward performance; they reward clarity—clean HTML, accurate metadata, and structured relationships between pages.
Astro’s island architecture makes pages feel instant, but if your meta tags or sitemaps are off, Google still has to guess what’s inside.
That’s why technical SEO is just as important in Astro as in any JavaScript framework; only here, you’re working from a stronger base.
2. Rendering Models: The Astro Advantage
Astro flips the rendering problem on its head: it ships zero JavaScript by default and hydrates components only where needed.
That means Googlebot receives full, clean HTML—no hydration delay, no broken metadata, no client-side rendering headaches.
| Model | SEO Impact | Typical Use Case |
|---|---|---|
| Static (SSG) | Best crawlability and performance. | Blogs, docs, landing pages |
| Hybrid (SSR) | Still SEO-friendly, as Astro serverside-renders full HTML. | Dynamic catalogs, search results |
| Islands (Partial Hydration) | Small interactive areas without hurting indexability. | Product cards, carts, forms |
Use SSG for content that changes on editorial cadence. Use SSR only when the HTML must reflect request-time data: availability, personalization, authenticated state, regional pricing, or preview mode.
Rule of thumb: Let Astro handle static HTML by default, and hydrate only what’s truly interactive. Every unused <script> is one less millisecond in your LCP.
Explore web rendering options and solutions.
3. Core Web Vitals: Performance Is the Starting Line
Astro sites often crush Lighthouse scores straight out of npm create astro@latest, but it’s worth locking down a few habits:
- LCP under 2.5s: preload hero images and fonts. LCP is usually the hero image, video poster, or largest text block.
- INP under 200ms: keep client components tiny. INP problems usually come from hydrated islands, third-party scripts, consent tools, analytics, or heavy client-side search.
- CLS below 0.1: define image sizes and avoid late-loading elements. CLS often comes from missing image dimensions, late-loading banners, cookie notices, embeds, and ad slots.
Example:
<img src={Astro.resolve('../assets/hero.webp')} width="1200" height="600" alt="Hero image" loading="eager" />
Use Astro’s built-in image service:
---
import { Image } from 'astro:assets'
---
<Image src={hero} alt="Hero image" width={1200} height={600} format="webp" />
Combine that with compression (vite-plugin-compression), HTTP/2, and caching headers. Your SEO foundation becomes almost bulletproof.
Astro removes a lot of JavaScript by default. It does not protect you from bad marketing tags, oversized media, consent managers, or careless hydration.
4. Crawlability & Indexation
Astro makes serving static HTML trivial, but don’t forget the navigation signals crawlers rely on:
robots.txt
It controls crawling behavior; it is not a guaranteed visibility switch. If you want to appear in AI-assisted discovery, make sure important public content is crawlable, canonical, well-linked, and easy to summarize. If you want to opt out, define that policy explicitly.
npm install astro-robots-txt
import robotsTxt from 'astro-robots-txt';
export default {
integrations: [robotsTxt({
policy: [{ userAgent: '*', allow: '/' }],
sitemap: 'https://yourdomain.com/sitemap-index.xml'
})]
};
Sitemaps
npm install @astrojs/sitemap
import sitemap from '@astrojs/sitemap';
export default {
site: 'https://yourdomain.com',
integrations: [sitemap()],
};
Both update automatically on build, meaning your content and product URLs stay discoverable—no manual updates required.
@astrojs/sitemap generates entries from statically generated routes, but the official docs state it cannot generate sitemap entries for dynamic routes in SSR mode. That matters for commerce catalogs and large content sites using hybrid rendering.
llms.txt
llms.txt is not a ranking factor. Treat it as a machine-readable content map for AI retrieval systems, not as magic AI SEO dust.
Example:
# Example Brand
> Technical articles, product documentation, and commerce architecture guides.
## Core content
- [Blog](https://example.com/blog)
- [Docs](https://example.com/docs)
- [RSS](https://example.com/rss.xml)
## Important guides
- [Astro SEO Guide](https://example.com/blog/astro-seo)
- [Performance Guide](https://example.com/blog/frontend-performance)
5. Metadata & Structured Data
Every page in Astro can export metadata directly in its frontmatter:
import { getProduct } from '../lib/api';
const product = await getProduct(Astro.params.slug);
export const prerender = true;
export const seo = {
title: `${product.name} | My Store`,
description: product.shortDescription,
};
Then inject metadata inside <head>:
<head>
<title>{seo.title}</title>
<meta name="description" content={seo.description} />
<link rel="canonical" href={`https://yourdomain.com${Astro.url.pathname}`} />
</head>
For structured data:
export function getArticleSchema(post) {
return {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
description: post.description,
image: {
'@type': 'ImageObject',
url: post.image.url,
width: 1200,
height: 630,
},
datePublished: post.publishedAt,
dateModified: post.updatedAt ?? post.publishedAt,
author: {
'@type': 'Person',
name: post.author.name,
url: post.author.url,
sameAs: post.author.sameAs,
},
publisher: {
'@type': 'Organization',
name: 'Your Brand',
logo: {
'@type': 'ImageObject',
url: 'https://example.com/logo.png',
width: 600,
height: 60,
},
},
mainEntityOfPage: post.canonical,
};
}
Rich results often come down to clean, consistent markup like this. But structured data does not guarantee rich results; it makes the page eligible and easier to understand. If you’re using a headless commerce like Crystallize or a CMS like Contentful, you can inject data via Astro’s server-side GraphQL calls.
Open Graph and social cards.
<meta property="og:title" content={seo.title} />
<meta property="og:description" content={seo.description} />
<meta property="og:type" content="article" />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={seo.image} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={seo.title} />
<meta name="twitter:description" content={seo.description} />
<meta name="twitter:image" content={seo.image} />
6. URL Structure and Routing Hygiene
Astro’s routing is file-based and static—great news for SEO.
✅ Use human-readable slugs:
/blog/how-astro-handles-hydration/
/shop/coffee-beans/ethiopian-blend/
✅ Configure canonical URLs globally:
// src/components/Canonical.astro
<link rel="canonical" href={`https://yourdomain.com${Astro.url.pathname}`} />
Avoid:
- Query parameters for canonical pages
- Duplicate
/index.htmlor/variations - Trailing slash inconsistencies (
astro.config.mjs→trailingSlash: 'always'or'never')
Predictable paths reduce duplicate URL risk and make internal linking easier to reason about.” Crawl budget is real, but for most small/medium Astro sites, duplicate handling and clarity matter more.
Canonicals deserve their own high-priority mention. This is not a small routing hygiene detail. It is the control point for duplicate URLs, trailing-slash inconsistencies, query parameters, paginated pages, and social-sharing alignment.
---
const canonical = new URL(Astro.url.pathname, Astro.site).toString();
---
<link rel="canonical" href={canonical} />
<meta property="og:url" content={canonical} />
Your canonical URL and og:url should agree. If Google indexes one URL while LinkedIn, Slack, or X cache another, you split signals across versions of the same page.
7. Image Optimization & Lazy Loading
Images often make or break both UX and rankings. Astro’s image service converts, resizes, and optimizes assets automatically:
---
import { Image } from 'astro:assets';
import hero from '../assets/hero.webp';
---
<Image
src={hero}
alt="Descriptive image alt text"
width={1200}
height={600}
loading="eager"
decoding="async"
/>
Tips:
- Always set alt attributes (semantic relevance matters).
- Don’t lazy-load above-the-fold media.
- Cache assets at the CDN edge (Netlify, Vercel, or Cloudflare Pages).
8. Dynamic Content & Revalidation
Static generation is perfect for blogs—but what about stores, catalogs, or fast-changing data?
Astro supports SSR with caching and revalidation, keeping both performance and freshness in play.
Example:
export const prerender = false;
export async function get({ params }) {
const product = await getProduct(params.slug);
return {
body: JSON.stringify(product),
headers: { 'Cache-Control': 'public, max-age=3600' }
};
}
This ensures crawlers always see up-to-date HTML without waiting for rebuilds.
9. Analytics & Script Strategy
Astro discourages unnecessary JavaScript—and that’s good SEO hygiene. When you do need tracking, load it responsibly. Say you use Plausible as your analytics tool:
<script is:inline async src="https://plausible.io/js/script.js" data-domain="yourdomain.com"></script>
If you must, or rather if you are packed with 3rd party scripts, to keep websites loading fast, use Google Tag Manager.
Example:
<Script type="text/partytown">
{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXX');`}
</Script>
Alternatively, consider Partytown to offload heavy third-party scripts to web workers and preserve performance.
Third-party scripts are now the place where good Astro sites go to lose their advantage.
10. Debugging & QA
Finally, before going live, run a few sanity checks:
- Crawl simulation – Use Screaming Frog or Sitebulb with JavaScript rendering off.
- Rich result testing – Validate JSON-LD markup.
- Core Web Vitals – Test on mobile using PageSpeed Insights.
- Sitemap inspection – Confirm that newly added pages appear in your sitemap.
- Cache headers – Check Cache-Control responses to avoid stale content. Remember: a build that’s “green” locally can still fail SEO audits if you forget canonical consistency or structured data validation.
11. FAQs
Do Astro sites rank better automatically?
They often do, but only if metadata and routing are handled correctly. Performance alone doesn’t guarantee SEO.
Is Astro good for eCommerce?
Yes. Combine static builds for categories with SSR for product availability, and you get the best of both worlds. Check out a Crystallize + Astro demo store built for speed and structure as an example.
What about multilingual or region-based sites?
Use hreflang tags and folder-based routes. Static output makes localization simple and SEO-safe.
Want to learn more SEO?
Continue with my original and complete eCommerce SEO Guide: How To Drive Organic Traffic In 2026?
Astro solved the performance problem. The rest—metadata, structured content, and crawl clarity—is up to you.
When you combine its speed with intelligent information architecture, your site stops just being fast… it becomes findable.
Top comments (4)
Great article — thanks for sharing! 🙌
I really appreciate how you break down how Astro isn’t just about speed but also about crawlability and SEO-friendliness. A few things that stood out:
One question though: when you recommend using SSR in Astro for dynamic content (and still staying SEO-friendly) — are there any specific pitfalls you’ve seen (e.g., caching strategy, edge vs origin)? I’d love to hear more about your experience in production with that.
Thanks again — this gives me new ideas for how to rethink the SEO setup of my next project! 🧠
General rule is to use SSR only where freshness is required. As for the biggest Astro SSR pitfall probably caching the wrong thing and fragmenting the cache.
The Astro team are working to implement Route Caching for SSR which simplify the caching with SWR.
github.com/withastro/roadmap/pull/...
I'm running almost no-js websites with GTM (mostly ga, hotjar, some pixels) and OneTrust. Unfortunately partytown is completely incompatible with OT (whether or not the OT is ran from GTM). Lighthouse takes a huge hit from this, up to 20 points. OT JS is even taking down SEO score.
Any ideas how to deal with this kind of setup?