Astro & SEO: Why Static Sites Rank Better
In short
SEO with Astro means static HTML as the base plus a handful of things you set deliberately in code – meta tags and canonical in the layout, @astrojs/sitemap, robots.txt, JSON-LD per page type, length rules via a content collection schema, astro:assets for images, a fixed trailing-slash rule and hreflang for multiple languages. No SEO plugin needed. What Astro does not deliver is content, backlinks and search volume – that stays editorial work.
“Astro is great for SEO” – you read that often. And it is true – but only halfway. Astro solves real SEO problems at the technical level: static HTML, minimal JavaScript, strong Core Web Vitals. What it does not solve: the content work that Google actually rewards. This website runs on Astro, bilingual, fully static. What we measured and learned in the process is what this article is based on.
The article explains without hype why static architecture is technically preferred, where the limits are, and how Astro honestly stacks up against WordPress – based on real experience, not theory.
Why Astro Is SEO-Friendly
When Google crawls a page, the crawler first requests the raw HTML document. What it receives determines what gets indexed. With a static Astro page, it gets exactly that: complete, directly readable HTML – no waiting for JavaScript execution, no server-side rendering overhead, no detour through a PHP interpreter.
This is not coincidence – it is the architecture: Astro pre-builds pages at build time. At runtime there is no rendering left to do – the HTML is finished and delivered directly. That has three direct SEO consequences:
Crawlers see all content immediately. Google’s crawler can execute JavaScript, but there is a two-stage indexing process: first the HTML, later – with unknown delay – the rendered JavaScript content. With Astro, that delay does not exist.
Core Web Vitals come out well by default. The Largest Contentful Paint (LCP) – the first visible, large element – loads significantly earlier on static pages because nothing has to wait for server-side processing or client-side rendering. Our own site achieves an LCP of 0.4 seconds on desktop, without having explicitly optimized for it.
Little JavaScript means little blocking. Astro sends no JavaScript to the browser by default – unless explicitly requested (Islands Architecture). What is not loaded cannot block rendering. The Total Blocking Time (TBT) on our site is 0 ms.
The Technical SEO Advantages in Detail
Load Time and LCP
LCP is the centerpiece of Core Web Vitals scoring. Google counts Core Web Vitals among the page experience signals that feed into ranking – but its Google Search Central documentation also stresses that relevant, helpful content weighs more. Fast loading does not replace content, but it is an advantage when content is comparably good. With static pages, LCP is structurally better: the page is not first assembled on the server or rendered in the browser, but sits ready as HTML – on the web server or in a CDN. That produces fast response times – even on simpler hosting infrastructure.
What this looks like in practice we have documented in detail in 100/100 PageSpeed with Astro – with real measurements from this website.
No Client-Rendering Problem
With JavaScript-heavy frameworks or classic WordPress with a page builder, a common problem arises: the browser first receives an almost empty HTML document and then builds the page via JavaScript. Google can handle this in principle, but there are risks: rendering errors, too-slow execution, missing meta tags at crawl time. With Astro, none of that happens. The crawler always receives the complete, final document.
This matters especially for Open Graph tags and JSON-LD structured data: if these are only set by JavaScript, the crawler can miss them on the first visit. With Astro they are in the static HTML – no ifs or buts.
Astro SEO in Practice: What to Set Up in Code
Static HTML is the foundation. But a page only ranks once titles, canonicals, sitemap, structured data and images are right. In Astro each of these is a few lines of code, but you have to set them deliberately. The examples below are shortened; we checked the APIs against the Astro documentation (Astro 7).
Meta Tags and Canonical in the Layout
A single layout component renders the <head> for every page. The page passes title and description as props, and the canonical and social tags are derived from them:
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description: string;
noindex?: boolean;
}
const { title, description, noindex = false } = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site);
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
{noindex && <meta name="robots" content="noindex" />}
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<slot name="head" />
</head>
<body><slot /></body>
</html>
Astro.site is the site URL from your astro.config.mjs. Without it the value is undefined, so always set it. Because you only take the pathname, the canonical stays clean even when the page is requested with tracking parameters. An example of “canonical across two domains” is in Multilingual Astro: Two Domains, Two Languages, One Build.
Sitemap with @astrojs/sitemap
npx astro add sitemap
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com', // required
integrations: [
sitemap({
filter: (page) => !page.includes('/thanks/') && !page.includes('/imprint/'),
}),
],
});
The integration writes sitemap-index.xml and sitemap-0.xml at build time. filter removes pages that should stay out of search. You can set changefreq and priority, but the Astro documentation itself notes that Google ignores them. What matters more is that only indexable URLs are listed: no redirects, no noindex pages.
robots.txt
The simplest solution is a file at public/robots.txt. If you want to derive the sitemap URL from the site setting, use an endpoint:
// src/pages/robots.txt.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = ({ site }) => {
const sitemap = new URL('sitemap-index.xml', site);
return new Response(`User-agent: *\nAllow: /\n\nSitemap: ${sitemap.href}\n`);
};
The file name determines the output format: robots.txt.ts becomes /robots.txt at build time. Keep in mind that Disallow in robots.txt prevents crawling, not reliably indexing. A page that should stay out of search gets noindex in its HTML and remains crawlable.
Structured Data per Page Type
JSON-LD belongs in the static HTML. You build a small component that serializes an object:
---
// src/components/JsonLd.astro
const { data } = Astro.props;
---
<script type="application/ld+json" set:html={JSON.stringify(data)} />
Astro only bundles <script> tags without attributes; a tag with type="application/ld+json" stays in the HTML unchanged. Each page type gets different markup:
- Home page or company page:
Organizationwith name, URL, logo and contact details. - Article:
BlogPostingwithheadline,datePublished,dateModified,authorand, where available,abstract. The data comes from the frontmatter, not from a second place to maintain. - Pages with breadcrumbs:
BreadcrumbList. - Frequently asked questions:
FAQPage, from the same data as the visible FAQ.
The markup must match the visible content. Structured data that does not appear on the page does not help and can do harm. You can check the result with Google’s Rich Results Test.
Content Collections: Enforce Lengths in the Schema
The most reliable way to avoid bad meta data: the build fails when it does not fit. Content collections validate frontmatter with a Zod schema:
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const articles = defineCollection({
loader: glob({ base: './src/content/articles', pattern: '**/*.{md,mdx}' }),
schema: z.object({
title: z.string().min(10).max(70),
description: z.string().min(120).max(160),
publishedAt: z.coerce.date(),
updatedAt: z.coerce.date().optional(),
}),
});
export const collections = { articles };
If an article violates the schema, Astro reports the error at build time, before anything goes live. We use this for all our insights: title, description and summary have fixed limits, and a forgotten field is caught before anyone sees it. The same data then feeds meta tags, Open Graph and the JSON-LD from the layout.
Images with astro:assets
Images are usually the LCP candidate. The <Image /> component from astro:assets optimizes local images at build time and sets width and height, which prevents layout shift (CLS):
---
import { Image } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Image src={hero} alt="The team in front of the office" format="webp" fetchpriority="high" loading="eager" />
Load the image above the fold immediately and with high priority; all others keep the default (loading="lazy"). A meaningful alt text is mandatory (the component requires it) and also helps image search. Astro only optimizes remote images if you authorize the source under image.domains or image.remotePatterns in the config. How this affects the measurements is covered in 100/100 PageSpeed with Astro.
Trailing Slash and Redirects
A page should be reachable under exactly one URL. Otherwise /services and /services/ compete with each other. Decide:
// astro.config.mjs
export default defineConfig({
trailingSlash: 'always', // or 'never'; default is 'ignore'
redirects: {
'/old-path/': '/new-path/',
},
});
With 'always' or 'never', Astro fixes the spelling for routing. Whether the wrong variant is also redirected on static hosting is decided by your web server – check it once with curl -I. Important for redirects: without an adapter, Astro produces only HTML files with <meta http-equiv="refresh"> in a static build. That is not a real 301. For a proper 301, also add the redirect to the server configuration (in .htaccess on Apache) or use an adapter that writes it into your host’s configuration. For page moves we maintain both, because a move without a real 301 can cost rankings.
Multilingual Sites and hreflang
A bilingual site needs three things: its own URL per language, a canonical pointing to the page’s own language version, and reciprocal hreflang links:
<link rel="alternate" hreflang="de" href="https://example.de/leistungen/" />
<link rel="alternate" hreflang="en" href="https://example.com/services/" />
<link rel="alternate" hreflang="x-default" href="https://example.de/leistungen/" />
The links must point in both directions, and every page also references itself. Only set them when the other language version really exists. Astro brings routing with its i18n configuration (locales, defaultLocale, routing.prefixDefaultLocale), and getRelativeLocaleUrl() from astro:i18n builds the links. The sitemap integration can also emit xhtml:link entries with hreflang through its i18n option. The full setup with two domains is described in Multilingual Astro: Two Domains, Two Languages, One Build.
Islands, View Transitions and Crawlability
Without a client:* directive, Astro renders every UI component to plain HTML and CSS. With client:load, client:idle, client:visible or client:media, only that single island hydrates. The server-rendered content is still in the HTML, so a crawler sees it. client:only is different: nothing is rendered ahead of time, and the component only appears in the browser. Anything that should rank (headings, body text, internal links) therefore does not belong in a client:only island.
For transitions between pages there is <ClientRouter /> from astro:transitions. It fetches the next page and swaps the content in the browser. In our view this changes little for crawlers, because every URL still returns complete HTML when requested directly and your links remain normal <a href> tags. The Astro documentation does not explicitly address crawler behavior; that is our assessment, not a guarantee from Google. If you only want a transition effect, the native CSS feature @view-transition also works and needs no router. More JavaScript always means more load for Core Web Vitals; how to measure that is covered in 100/100 PageSpeed with Astro.
llms.txt in Brief
An llms.txt is a machine-readable overview of your most important pages for AI systems. In Astro you build it as an endpoint (src/pages/llms.txt.ts) from the same data as your navigation and sitemap instead of maintaining it by hand. What it does and does not achieve is covered in llms.txt in Practice.
Where Astro Alone Is Not Enough
Here is the point that most framework articles leave out: technology is not SEO. Technology is the foundation on which SEO can work at all.
An Astro site that loads fast and is cleanly built but has little content will not convince Google. What Google actually evaluates is the combination of:
- Content that genuinely matches search queries. Keyword research, search intent, depth of information – that is editorial work, which no framework takes off your plate.
- Authority and backlinks. A new site, regardless of technology, needs time and external links to climb in the rankings. A good LCP does not help directly with this.
- Consistent maintenance. Regular new content, updating outdated pages, technical audits – that is operational work.
Astro ensures that the technology does not get in your way. That is a real advantage. But it is not a turbo that pushes thin content upwards. Anyone investing in organic search with Astro still has to do serious content work.
Astro SEO vs. WordPress SEO
We get this question often: “Is Astro better for SEO than WordPress?” The honest answer: technically yes, content-wise it is a draw.
WordPress has built a solid SEO reputation over decades – Yoast, RankMath and similar tools are mature, and millions of WordPress sites rank well. But WordPress carries technical baggage: PHP rendering on every request, plugin stacks that accumulate JavaScript, and an LCP curve that quickly drifts into the yellow without tuning. That is not a fundamental problem – we build WordPress sites ourselves that pass Core Web Vitals. But it takes consistent effort.
With Astro the technical starting position is better – not through subsequent optimization, but through architecture. The Lighthouse scores we achieve without any explicit SEO tuning would require substantial configuration effort to reproduce on WordPress.
What Astro lacks: a click-based interface for editors. Anyone running a site with changing content, multiple editors, and daily publishing needs either a headless CMS or stays sensibly on WordPress. The full comparison – including concrete decision criteria – is in Astro vs. WordPress.
For content-rich sites with a stable editorial process – exactly what many business websites are – Astro is the better technical starting point for SEO. Whether that fits your project, talk to us directly: Astro agency.
Frequently Asked Questions
Is Astro really good for SEO?
Yes – from a technical standpoint, Astro is one of the best foundations for SEO available right now. Static HTML, very little JavaScript, and strong Core Web Vitals scores hit exactly what Google prefers technically. What Astro does not deliver: content, backlinks, and search volume. That is human work – no framework replaces it.
Do I need an SEO plugin for Astro?
Unlike WordPress, Astro does not need a plugin to enable SEO. Meta tags, Open Graph data, and structured data are set directly in code – precisely and without plugin overhead. If you still want something ready-made, astro-seo or @astrojs/sitemap both work without performance costs.
How does Astro affect LCP?
Significantly. Because Astro delivers static HTML and skips client-side rendering, the first visible element is ready much earlier than on a rendered React or WordPress page. Our own website achieves an LCP of 0.4 seconds on desktop – without special optimization, purely through architecture.
Can Astro beat WordPress at SEO?
Technically yes, content-wise no – that is not a question of framework. An Astro site without content loses to a well-maintained WordPress site. An Astro site with solid content has better odds on Core Web Vitals, which Google factored in as a ranking signal. Anyone wanting both worlds combines Astro as the frontend with WordPress as a headless CMS.
How do I do SEO with Astro, step by step?
Set title, description and canonical in a shared layout. Generate the sitemap with @astrojs/sitemap and point to it from robots.txt. Output JSON-LD that matches the page type, enforce title and description lengths with a content collection schema and include images through astro:assets. Fix a trailing-slash rule and, for multiple languages, set hreflang in both directions.
Do View Transitions or JavaScript islands hurt SEO?
Not automatically. Components without a client: directive and islands with client:load, client:idle or client:visible are present as HTML in the document. client:only is different: nothing is rendered ahead of time. Important content and internal links therefore do not belong in such islands. The ClientRouter for view transitions does not change the fact that every URL returns complete HTML when requested directly.
Related Services
Founder & Full-Stack Developer
20+ years of web development experience. Specialised in Laravel, WordPress and custom software for mid-sized businesses.