Skip to content

Astro SEO Complete Guide

SEO (Search Engine Optimization) là yếu tố quyết định website của bạn có được tìm thấy trên Google hay không. Astro là framework tuyệt vờI cho SEO vì generate ra static HTML. Guide này cover tất cả aspects của SEO trên Astro.

src/components/SEO.astro:

---
export interface Props {
title: string;
description: string;
image?: string;
type?: string;
publishedTime?: string;
modifiedTime?: string;
author?: string;
tags?: string[];
}
const {
title,
description,
image = '/default-og-image.jpg',
type = 'website',
publishedTime,
modifiedTime,
author,
tags,
} = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content={type} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.site)} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, Astro.site)} />
<!-- Article specific -->
{type === 'article' && (
<>
{publishedTime && <meta property="article:published_time" content={publishedTime} />}
{modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
{author && <meta property="article:author" content={author} />}
{tags?.map(tag => <meta property="article:tag" content={tag} />)}
</>
)}

src/layouts/Layout.astro:

---
import SEO from '../components/SEO.astro';
interface Props {
title: string;
description: string;
image?: string;
}
const { title, description, image } = Astro.props;
---
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<SEO
title={title}
description={description}
image={image}
/>
<link rel="sitemap" href="/sitemap-index.xml" />
</head>
<body>
<slot />
</body>
</html>
Terminal window
npm install @astrojs/sitemap

astro.config.mjs:

import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://yoursite.com',
integrations: [
sitemap({
filter: (page) => !page.includes('/admin'),
changefreq: 'weekly',
priority: 0.7,
lastmod: new Date(),
i18n: {
defaultLocale: 'vi',
locales: {
vi: 'vi-VN',
en: 'en-US',
},
},
}),
],
});

src/pages/sitemap.xml.js:

import { getCollection } from 'astro:content';
export async function GET() {
const posts = await getCollection('blog');
const pages = [
{ url: '/', changefreq: 'daily', priority: 1.0 },
{ url: '/about', changefreq: 'monthly', priority: 0.8 },
];
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.map(page => `
<url>
<loc>https://yoursite.com${page.url}</loc>
<changefreq>${page.changefreq}</changefreq>
<priority>${page.priority}</priority>
</url>
`).join('')}
${posts.map(post => `
<url>
<loc>https://yoursite.com/blog/${post.slug}/</loc>
<lastmod>${post.data.date.toISOString()}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
`).join('')}
</urlset>`;
return new Response(sitemap, {
headers: {
'Content-Type': 'application/xml',
},
});
}

src/pages/rss.xml.js:

import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: 'My Astro Blog',
description: 'A blog about web development',
site: context.site,
items: posts.map((post) => ({
title: post.data.title,
pubDate: post.data.date,
description: post.data.excerpt,
link: `/blog/${post.slug}/`,
categories: post.data.tags,
author: post.data.author,
})),
customData: `<language>vi-vn</language>`,
});
}

src/components/Schema.astro:

---
interface Props {
type: 'website' | 'article' | 'person' | 'organization';
data: Record<string, any>;
}
const { type, data } = Astro.props;
const schemas = {
website: {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: data.title,
url: data.url,
description: data.description,
},
article: {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: data.title,
description: data.description,
image: data.image,
datePublished: data.date,
dateModified: data.modified || data.date,
author: {
'@type': 'Person',
name: data.author,
},
publisher: {
'@type': 'Organization',
name: 'Your Brand',
logo: {
'@type': 'ImageObject',
url: 'https://yoursite.com/logo.png',
},
},
},
person: {
'@context': 'https://schema.org',
'@type': 'Person',
name: data.name,
url: data.url,
image: data.image,
description: data.description,
sameAs: data.socials,
},
};
const schema = schemas[type] || schemas.website;
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
---
// Trong page
import Schema from '../components/Schema.astro';
---
<Schema
type="article"
data={{
title: post.data.title,
description: post.data.excerpt,
image: post.data.image,
date: post.data.date,
author: post.data.author,
}}
/>

public/robots.txt:

User-agent: *
Allow: /
# Disallow admin routes
Disallow: /admin
Disallow: /api
# Sitemap
Sitemap: https://yoursite.com/sitemap-index.xml
# Crawl-delay (optional)
Crawl-delay: 10

Hoặc generate dynamically:

src/pages/robots.txt.ts:

export function GET() {
const robots = `
User-agent: *
Allow: /
Sitemap: ${import.meta.env.SITE}/sitemap-index.xml
`.trim();
return new Response(robots, {
headers: { 'Content-Type': 'text/plain' },
});
}
---
import { Image } from 'astro:assets';
---
<!-- ✅ Tốt cho SEO -->
<Image
src={image}
alt="Mô tả chi tiết về hình ảnh"
width={800}
height={400}
/>
<!-- ❌ Không tốt cho SEO -->
<img src={image.src} />
<figure>
<Image
src={image}
alt={caption}
width={800}
height={400}
/>
<figcaption>{caption}</figcaption>
</figure>
✅ https://yoursite.com/blog/astro-seo-guide
✅ https://yoursite.com/products/laptop-dell-xps
❌ https://yoursite.com/p?id=123
❌ https://yoursite.com/page.php?cat=tech&id=456
astro.config.mjs
export default defineConfig({
trailingSlash: 'always', // hoặc 'never'
});

src/components/Breadcrumb.astro:

---
const { items } = Astro.props;
---
<nav aria-label="Breadcrumb">
<ol itemscope itemtype="https://schema.org/BreadcrumbList">
{items.map((item, index) => (
<li
itemprop="itemListElement"
itemscope
itemtype="https://schema.org/ListItem"
>
<a itemprop="item" href={item.url}>
<span itemprop="name">{item.label}</span>
</a>
<meta itemprop="position" content={index + 1} />
</li>
))}
</ol>
</nav>
---
const { currentPost, allPosts } = Astro.props;
const relatedPosts = allPosts
.filter(post =>
post.slug !== currentPost.slug &&
post.data.tags.some(tag => currentPost.data.tags.includes(tag))
)
.slice(0, 3);
---
{relatedPosts.length > 0 && (
<aside>
<h3>Bài viết liên quan</h3>
<ul>
{relatedPosts.map(post => (
<li>
<a href={`/blog/${post.slug}/`}>{post.data.title}</a>
</li>
))}
</ul>
</aside>
)}
MetricTargetCách cải thiện
LCP< 2.5sOptimize images, preload fonts
FID< 100msReduce JS, code splitting
CLS< 0.1Set image dimensions
<head>
<!-- Preconnect external domains -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- DNS prefetch -->
<link rel="dns-prefetch" href="https://analytics.example.com" />
</head>

astro.config.mjs:

export default defineConfig({
i18n: {
defaultLocale: 'vi',
locales: ['vi', 'en', 'ja'],
routing: {
prefixDefaultLocale: false,
},
},
});
<!-- Trong <head> -->
<link rel="alternate" hreflang="vi" href="https://yoursite.com/vi/page/" />
<link rel="alternate" hreflang="en" href="https://yoursite.com/en/page/" />
<link rel="alternate" hreflang="x-default" href="https://yoursite.com/page/" />
<!-- Trong <head> -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXX');
</script>
  1. Add property: https://search.google.com/search-console
  2. Verify ownership (HTML tag hoặc DNS)
  3. Submit sitemap
  4. Monitor performance
ToolMục đích
Google Search ConsoleMonitor SEO performance
Google AnalyticsTrack traffic
LighthouseAudit SEO & Performance
Screaming FrogCrawl & analyze site
Ahrefs/SEMrushKeyword research
Schema.orgStructured data validator