Astro's SEO optimization features are very powerful, helping developers build search engine-friendly websites. Understanding how to leverage Astro's SEO capabilities is crucial for improving website visibility.
Core SEO Advantages:
- Static HTML Output: Outputs pure HTML by default, easy for search engines to crawl
- Fast Loading Speed: Zero JavaScript by default, improves Core Web Vitals
- Server-Side Rendering: Supports SSR, ensuring dynamic content is also indexed
- Semantic HTML: Encourages using correct HTML tags
Meta Tags Configuration:
astro--- // src/pages/index.astro const title = "My Website Title"; const description = "This is the website description"; const image = "/og-image.jpg"; const url = new URL(Astro.url.pathname, Astro.site); --- <html lang="en"> <head> <!-- Basic Meta Tags --> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <meta name="description" content={description} /> <meta name="keywords" content="astro, seo, web development" /> <!-- Open Graph Tags --> <meta property="og:type" content="website" /> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /> <meta property="og:image" content={new URL(image, Astro.site)} /> <meta property="og:url" content={url} /> <!-- Twitter Card Tags --> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content={title} /> <meta name="twitter:description" content={description} /> <meta name="twitter:image" content={new URL(image, Astro.site)} /> <!-- Canonical Link --> <link rel="canonical" href={url} /> <!-- Favicon --> <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <!-- Structured Data --> <script type="application/ld+json" set:html={JSON.stringify({ "@context": "https://schema.org", "@type": "WebSite", "name": title, "url": url.toString(), "description": description })} /> </head> <body> <slot /> </body> </html>
Dynamic SEO Component:
astro--- // src/components/SEO.astro interface Props { title: string; description: string; image?: string; type?: 'website' | 'article'; publishedTime?: Date; modifiedTime?: Date; author?: string; } const { title, description, image = '/og-default.jpg', type = 'website', publishedTime, modifiedTime, author } = Astro.props; const url = new URL(Astro.url.pathname, Astro.site); const imageUrl = new URL(image, Astro.site); --- <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <meta name="description" content={description} /> <meta name="robots" content="index, follow" /> <!-- Open Graph --> <meta property="og:type" content={type} /> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /> <meta property="og:image" content={imageUrl} /> <meta property="og:url" content={url} /> {publishedTime && <meta property="article:published_time" content={publishedTime.toISOString()} />} {modifiedTime && <meta property="article:modified_time" content={modifiedTime.toISOString()} />} {author && <meta property="article:author" content={author} />} <!-- Twitter Card --> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content={title} /> <meta name="twitter:description" content={description} /> <meta name="twitter:image" content={imageUrl} /> <!-- Canonical --> <link rel="canonical" href={url} /> <!-- JSON-LD --> <script type="application/ld+json" set:html={JSON.stringify({ "@context": "https://schema.org", "@type": type === 'article' ? 'Article' : 'WebSite', "headline": title, "description": description, "image": imageUrl.toString(), "url": url.toString(), "datePublished": publishedTime?.toISOString(), "dateModified": modifiedTime?.toISOString(), "author": { "@type": "Person", "name": author } })} />
Using SEO Component:
astro--- // src/pages/blog/[slug].astro import SEO from '../../components/SEO.astro'; import { getEntry } from 'astro:content'; const post = await getEntry('blog', Astro.params.slug); const { Content } = await post.render(); --- <SEO title={post.data.title} description={post.data.description} image={post.data.image} type="article" publishedTime={post.data.publishDate} modifiedTime={post.data.updatedDate} author={post.data.author} /> <article> <h1>{post.data.title}</h1> <Content /> </article>
Sitemap Generation:
typescript// src/pages/sitemap.xml.ts import { getCollection } from 'astro:content'; export async function GET(context) { const posts = await getCollection('blog'); const site = context.site?.toString() || ''; const body = `<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ${posts.map(post => ` <url> <loc>${site}blog/${post.slug}</loc> <lastmod>${post.data.updatedDate || post.data.publishDate}</lastmod> <changefreq>weekly</changefreq> <priority>0.8</priority> </url>`).join('')} <url> <loc>${site}</loc> <lastmod>${new Date().toISOString()}</lastmod> <changefreq>daily</changefreq> <priority>1.0</priority> </url> </urlset>`; return new Response(body, { headers: { 'Content-Type': 'application/xml', 'Cache-Control': 'public, max-age=86400', }, }); }
Robots.txt Configuration:
typescript// src/pages/robots.txt.ts export async function GET(context) { const site = context.site?.toString() || ''; const body = `User-agent: * Allow: / Disallow: /api/ Disallow: /admin/ Sitemap: ${site}sitemap.xml`; return new Response(body, { headers: { 'Content-Type': 'text/plain', 'Cache-Control': 'public, max-age=86400', }, }); }
Structured Data:
astro--- // src/components/ArticleSchema.astro interface Props { title: string; description: string; image: string; publishDate: Date; author: string; url: string; } const { title, description, image, publishDate, author, url } = Astro.props; --- <script type="application/ld+json" set:html={JSON.stringify({ "@context": "https://schema.org", "@type": "Article", "headline": title, "description": description, "image": image, "datePublished": publishDate.toISOString(), "dateModified": publishDate.toISOString(), "author": { "@type": "Person", "name": author }, "publisher": { "@type": "Organization", "name": "My Website", "logo": { "@type": "ImageObject", "url": "/logo.png" } }, "mainEntityOfPage": { "@type": "WebPage", "@id": url } })} />
Breadcrumb Navigation:
astro--- // src/components/Breadcrumb.astro interface Props { items: Array<{ name: string; href: string; }>; } const { items } = Astro.props; --- <nav aria-label="Breadcrumb"> <ol class="breadcrumb"> {items.map((item, index) => ( <li class="breadcrumb-item"> {index === items.length - 1 ? ( <span aria-current="page">{item.name}</span> ) : ( <a href={item.href}>{item.name}</a> )} </li> ))} </ol> </nav> <script type="application/ld+json" set:html={JSON.stringify({ "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": items.map((item, index) => ({ "@type": "ListItem", "position": index + 1, "name": item.name, "item": new URL(item.href, Astro.site).toString() })) })} /> <style> .breadcrumb { display: flex; list-style: none; padding: 0; margin: 1rem 0; } .breadcrumb-item:not(:last-child)::after { content: ' / '; margin: 0 0.5rem; } .breadcrumb-item a { color: #0066cc; text-decoration: none; } .breadcrumb-item a:hover { text-decoration: underline; } </style>
Performance and SEO:
astro--- // src/pages/index.astro import { Image } from 'astro:assets'; import heroImage from '../assets/hero.jpg'; --- <!-- Optimize image loading --> <Image src={heroImage} alt="Hero Image" width={1200} height={630} format="webp" loading="eager" priority={true} /> <!-- Preload critical resources --> <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin /> <link rel="preconnect" href="https://api.example.com" /> <!-- Inline critical CSS --> <style> /* Critical CSS */ .hero { min-height: 60vh; } </style>
Best Practices:
-
Meta Tags:
- Set unique title and description for each page
- Use Open Graph and Twitter Cards
- Set canonical links to avoid duplicate content
-
Structured Data:
- Use JSON-LD format
- Implement correct Schema types
- Validate with Google Structured Data Testing Tool
-
Performance Optimization:
- Optimize Core Web Vitals
- Use image optimization
- Implement code splitting
-
Content Optimization:
- Use semantic HTML
- Optimize heading hierarchy (H1-H6)
- Provide meaningful alt text
-
Technical SEO:
- Generate sitemap
- Configure robots.txt
- Implement breadcrumb navigation
Astro's SEO features help developers build search engine-friendly websites and improve online visibility.