关于 publishing-astro-websites
这项技能介绍如何用 Astro 构建内容型静态网站,面向网站开发者。内容涵盖集合管理、搜索、文档、多语言和部署实践。
主要功能
- 01Astro 项目搭建与渲染模式
- 02Markdown 内容集合与加载器
- 03代码高亮与图表集成
- 04使用 Pagefind 或 Fuse.js 搜索
- 05文档、多语言与常见页面模式
- 06构建、部署和测试指引
适用场景
- 01搭建博客或文档网站
- 02为静态网站添加搜索
- 03发布多版本或多语言文档
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ spillwavesolutions/ publishing-astro-websites-agentic-skill --skill publishing-astro-websites安装后在 Claude Code 中输入/publishing-astro-websites确认已加载。
附带文件references/deployment-platforms.mdreferences/markdown-deep-dive.md
详细介绍
发布 Astro 网站
使用 Astro 的零运行时 SSG 方案、局部水合和全面的 Markdown 支持,构建快速、以内容为驱动的静态网站。
目录
- 快速入门
- 不适用场景
- 项目结构
- SSG 与 SSR 与混合模式
- 内容集合 — 旧版、Content Layer API、自定义加载器
- 语法高亮 — Shiki、转换器、Expressive Code
- 图表集成 — Mermaid、PlantUML、深色模式主题
- 客户端搜索 — Pagefind(控件、权重)、Fuse.js
- 版本化文档 — Starlight、多版本
- 国际化 — 路由、回退
- 常见模式 — 分页、标签、RSS、表单
- 性能最佳实践 — 预获取、关键 CSS
- 部署 — Firebase URL 配置、GitHub Pages
- 部署前检查清单
- 测试与质量 — Vitest、Playwright、链接检查
- 故障排除
快速入门
# Create new project (use Blog template for Markdown sites)
npm create astro@latest
# Development
npm run dev # Local server at http://localhost:4321
npm run build # Generate static files in dist/
npm run preview # Preview production build
不适用场景
本技能专注于静态站点生成(SSG)。以下情况可考虑其他方案:
- 实时数据应用 - 使用带数据库连接的 SSR 模式
- 用户身份验证流程 - 需要服务端会话处理
- 库存动态变化的电子商务 - 使用混合模式或完整 SSR
- 单页应用(SPA) - 可直接考虑 React/Vue 框架
有关 SSG+SSR 混合模式,请参阅 Astro 的适配器文档。
项目结构
src/
components/ # Astro, React, Vue, Svelte components
content/ # Content Collections (Markdown/MDX)
config.ts # Collection schemas
docs/ # Example collection
layouts/ # Page wrappers with slots
pages/ # File-based routing
public/ # Static assets (images, fonts, favicons)
astro.config.mjs # Framework configuration
SSG 与 SSR 与混合模式
| 模式 | 页面渲染时机 | 用例 |
|---|---|---|
| SSG(默认) | 构建时 | 博客、文档、营销网站 |
| SSR | 每次请求时 | 动态数据、个性化 |
| 混合模式 | 两者结合 | 静态页面 + 动态端点 |
纯静态网站使用默认的 output: 'static',无需适配器。
内容集合
旧版模式(Astro 4.x)
在 src/content/config.ts 中定义架构:
import { defineCollection, z } from "astro:content";
export const collections = {
docs: defineCollection({
schema: z.object({
title: z.string(),
description: z.string().optional(),
tags: z.array(z.string()).optional(),
order: z.number().optional(),
draft: z.boolean().default(false)
})
})
};
Content Layer API(Astro 5.0+)
采用 glob() 加载器的新模式,大型网站的构建速度最高可提升 75%:
// src/content.config.ts (note: different filename)
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/data/blog' }),
schema: ({ image }) => z.object({
title: z.string(),
pubDate: z.coerce.date(),
draft: z.boolean().default(false),
cover: image(), // Validates image exists
author: reference('authors'), // Cross-collection reference
})
});
export const collections = { blog };
高级架构模式
schema: ({ image }) => z.object({
cover: image(), // Validates image in src/
category: z.enum(['tech', 'news']),
author: reference('authors'), // Cross-collection ref
relatedPosts: z.array(reference('blog')).optional(),
})
自定义加载器(远程内容)
从外部 API 获取内容(GitHub 发布版本、CMS 等):
// src/loaders/github-releases.ts
import type { Loader } from 'astro/loaders';
export function githubReleasesLoader(repo: string): Loader {
return {
name: 'github-releases',
load: async ({ store, logger }) => {
logger.info(`Fetching releases for ${repo}`);
const response = await fetch(`https://api.github.com/repos/${repo}/releases`);
const releases = await response.json();
for (const release of releases) {
store.set({
id: release.tag_name,
data: {
version: release.tag_name,
published_at: release.published_at,
body: release.body // Markdown release notes
}
});
}
}
};
}
在 content.config.ts 中注册:
import { githubReleasesLoader } from './loaders/github-releases';
const releases = defineCollection({
loader: githubReleasesLoader('owner/repo'),
schema: z.object({
version: z.string(),
published_at: z.string(),
body: z.string(),
})
});
查询并渲染集合:
---
import { getCollection } from "astro:content";
export async function getStaticPaths() {
const docs = await getCollection("docs");
return docs.map(doc => ({
params: { slug: doc.slug },
props: { doc }
}));
}
const { doc } = Astro.props;
const { Content } = await doc.render();
---
<article>
<h1>{doc.data.title}</h1>
<Content />
</article>
语法高亮
基本 Shiki 配置
import { defineConfig } from "astro/config";
export default defineConfig({
markdown: {
shikiConfig: {
theme: "github-dark",
wrap: true
}
}
});
浅色/深色双主题
shikiConfig: {
themes: {
light: 'github-light',
dark: 'github-dark',
},
}
添加 CSS 以切换主题:
@media (prefers-color-scheme: dark) {
.astro-code, .astro-code span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
}
}
行高亮与转换器
```typescript {2,4}
const a = 1;
const b = 2; // highlighted
const c = 3;
console.log(a + b + c); // highlighted
```
Shiki 转换器(Astro 4.14+):
import { transformerNotationFocus, transformerNotationDiff } from '@shikijs/transformers';
shikiConfig: {
transformers: [transformerNotationFocus(), transformerNotationDiff()],
}
在代码中使用标记注释:
// [!code focus]- 聚焦此行// [!code ++]- 标记为新增内容(绿色)// [!code --]- 标记为删除内容(红色)
Expressive Code(文档推荐)
支持复制按钮、文件名和差异高亮的丰富代码块:
npm install astro-expressive-code
import expressiveCode from 'astro-expressive-code';
export default defineConfig({
integrations: [expressiveCode()],
});
功能:复制按钮、文件标签页、行标记、终端边框、文本标记。
图表集成
Mermaid(推荐)
安装 Astro 集成:
npm install astro-mermaid mermaid
// astro.config.mjs
import { defineConfig } from 'astro/config';
import mermaid from 'astro-mermaid';
export default defineConfig({
integrations: [mermaid({ theme: 'default' })]
});
在 Markdown 中使用:
```mermaid
graph TD;
A-->B;
B-->C;
```
功能:客户端渲染、自动切换主题、支持离线使用,无需 Playwright。
替代方案(构建时生成静态 SVG): 使用 rehype-mermaid 和 Playwright 预渲染图表(需要运行 npx playwright install --with-deps)。
深色模式主题策略:
- CSS 变量 - 由浏览器在运行时解析颜色:
// mermaid config
mermaid.initialize({
theme: 'base',
themeVariables: {
primaryColor: 'var(--diagram-primary)',
lineColor: 'var(--diagram-line)'
}
});
- Picture 元素 - 生成两种主题,并通过媒体查询切换:
<picture>
<source srcset="/diagrams/flow-dark.svg" media="(prefers-color-scheme: dark)">
<img src="/diagrams/flow-light.svg" alt="Flow diagram">
</picture>
- 内联 SVG - 使用 CSS 选择 SVG 类(风险:样式冲突):
.dark .mermaid-svg .node rect {
fill: var(--bg-dark);
}
PlantUML
npx astro add plantuml
在 Markdown 中使用:
```plantuml
@startuml
Alice -> Bob: Hello
Bob --> Alice: Hi!
@enduml
```
客户端搜索
Pagefind(大型网站推荐)
零配置静态搜索,在构建时建立索引:
npm install pagefind
添加到 package.json:
{
"scripts": {
"build": "astro build && npx pagefind --site dist",
"postbuild": "pagefind --site dist"
}
}
在组件中使用:
<link href="/pagefind/pagefind-ui.css" rel="stylesheet" />
<script src="/pagefind/pagefind-ui.js" type="text/javascript"></script>
<div id="search"></div>
<script>
window.addEventListener('DOMContentLoaded', () => {
new PagefindUI({ element: '#search', showSubResults: true });
});
</script>
功能:无需外部服务、可离线运行、自动建立索引、打包体积小(约 10KB)。
精细控制索引:
<main data-pagefind-body>
<h1 data-pagefind-meta="title">Page Title</h1>
<p data-pagefind-weight="10">Important intro text</p>
<nav data-pagefind-ignore>
<a href="/related">Related Posts</a>
</nav>
</main>
| 属性 | 用途 |
|---|---|
data-pagefind-body | 仅将此元素加入索引 |
data-pagefind-ignore | 将元素排除在索引之外 |
data-pagefind-meta="key" | 定义元数据字段 |
data-pagefind-weight="10" | 提高相关性权重(默认值:1) |
Pagefind 与 Fuse.js 对比
| 功能 | Pagefind | Fuse.js |
|---|---|---|
| 架构 | 预构建的二进制分块 | 运行时驻留内存 |
| 带宽 | 低(仅加载所需分块) | 高(下载完整索引) |
| 可扩展性 | 10,000+ 个页面 | < 500 个页面 |
| 多语言 | 原生词干提取 | 手动配置 |
| 用途 | 全站搜索 | 小型列表筛选 |
Fuse.js(轻量替代方案)
适用于需要自定义 UI 的较小网站:
---
import { getCollection } from "astro:content";
const posts = await getCollection('blog');
const searchIndex = JSON.stringify(posts.map(post => ({
title: post.data.title,
slug: post.slug,
body: post.body.slice(0, 500)
})));
---
<input type="search" id="search" placeholder="Search..." />
<ul id="results"></ul>
<script define:vars={{ searchIndex }}>
import Fuse from 'fuse.js';
const fuse = new Fuse(JSON.parse(searchIndex), {
keys: ['title', 'body'],
threshold: 0.3
});
document.getElementById('search').addEventListener('input', (e) => {
const results = fuse.search(e.target.value);
const resultsEl = document.getElementById('results');
// Clear previous results safely
resultsEl.replaceChildren();
// Build results using safe DOM methods
results.forEach(r => {
const li = document.createElement('li');
const link = document.createElement('a');
link.href = `/blog/${r.item.slug}`;
link.textContent = r.item.title;
li.appendChild(link);
resultsEl.appendChild(li);
});
});
</script>
对于企业需求,可考虑 Algolia(托管搜索 API)。
版本化文档
Starlight(文档网站推荐)
基于 Astro 构建的专用文档框架:
npm create astro@latest -- --template starlight
主要功能:内置搜索(Pagefind)、i18n、侧边栏导航、深色模式、组件覆盖。
// astro.config.mjs
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
export default defineConfig({
integrations: [
starlight({
title: 'My Docs',
sidebar: [
{ label: 'Guides', autogenerate: { directory: 'guides' } },
{ label: 'Reference', autogenerate: { directory: 'reference' } },
],
}),
],
});
多版本文档模式
使用基于文件夹的结构:
src/content/docs/
v1/
getting-started.md
api-reference.md
v2/
getting-started.md
api-reference.md
new-feature.md
对于 Starlight 版本管理,请使用 starlight-utils 插件:
npm install starlight-utils
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import starlightUtils from 'starlight-utils';
export default defineConfig({
integrations: [
starlight({
plugins: [starlightUtils({ multiSidebar: { switcherStyle: 'dropdown' } })],
sidebar: [
{ label: 'v2', items: [{ label: 'Guides', autogenerate: { directory: 'v2' } }] },
{ label: 'v1', items: [{ label: 'Guides', autogenerate: { directory: 'v1' } }] },
],
}),
],
});
国际化(i18n)
在 astro.config.mjs 中配置:
export default defineConfig({
i18n: {
defaultLocale: "en",
locales: ["en", "fr", "es"],
routing: {
prefixDefaultLocale: false
}
}
});
按区域设置组织内容:
src/content/docs/
en/
getting-started.md
fr/
getting-started.md
在组件中检测区域设置:
---
const locale = Astro.currentLocale || 'en';
---
缺少翻译时的回退方案
如果没有对应译文,则显示英文内容并附上横幅:
---
// src/pages/[lang]/[...slug].astro
import { getCollection, getEntry } from "astro:content";
const languages = ['en', 'es', 'fr'];
const defaultLang = 'en';
export async function getStaticPaths() {
const englishDocs = await getCollection('docs', ({ id }) => id.startsWith('en/'));
const paths = [];
for (const doc of englishDocs) {
const slug = doc.id.replace(/^en\//, '');
for (const lang of languages) {
const localizedId = `${lang}/${slug}`;
const localizedDoc = await getEntry('docs', localizedId);
paths.push({
params: { lang, slug },
props: {
entry: localizedDoc || doc, // Fallback to English
isFallback: !localizedDoc
}
});
}
}
return paths;
}
const { entry, isFallback } = Astro.props;
const { Content } = await entry.render();
---
{isFallback && (
<div class="translation-notice">
This page is not yet available in your language.
</div>
)}
<Content />
常见模式
分页列表
对于有 50+ 篇文章的网站,将内容拆分成多个页面:
---
// src/pages/blog/page/[page].astro
import { getCollection } from "astro:content";
const POSTS_PER_PAGE = 10;
export async function getStaticPaths() {
const allPosts = await getCollection("blog");
const totalPages = Math.ceil(allPosts.length / POSTS_PER_PAGE);
return Array.from({ length: totalPages }, (_, i) => ({
params: { page: String(i + 1) },
}));
}
const { page } = Astro.params;
const pageNum = parseInt(page);
const allPosts = await getCollection("blog");
const sortedPosts = allPosts.sort((a, b) =>
b.data.pubDate.getTime() - a.data.pubDate.getTime()
);
const start = (pageNum - 1) * POSTS_PER_PAGE;
const posts = sortedPosts.slice(start, start + POSTS_PER_PAGE);
const totalPages = Math.ceil(allPosts.length / POSTS_PER_PAGE);
---
{posts.map(post => <article>{post.data.title}</article>)}
<nav>
{pageNum > 1 && <a href={`/blog/page/${pageNum - 1}`}>← Previous</a>}
<span>Page {pageNum} of {totalPages}</span>
{pageNum < totalPages && <a href={`/blog/page/${pageNum + 1}`}>Next →</a>}
</nav>
标签/分类归档
为每个标签生成一个页面:
---
// src/pages/tags/[tag].astro
import { getCollection } from "astro:content";
export async function getStaticPaths() {
const allPosts = await getCollection("blog");
const tags = new Set();
allPosts.forEach(post => post.data.tags?.forEach(tag => tags.add(tag)));
return Array.from(tags).map(tag => ({
params: { tag },
props: { tag },
}));
}
const { tag } = Astro.props;
const allPosts = await getCollection("blog");
const postsWithTag = allPosts.filter(post => post.data.tags?.includes(tag));
---
<h1>Posts tagged: {tag}</h1>
{postsWithTag.map(post => <a href={`/blog/${post.slug}`}>{post.data.title}</a>)}
RSS 订阅源
// src/pages/rss.xml.js
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context) {
const blog = await getCollection("blog");
return rss({
title: "My Blog",
description: "A blog about Astro",
site: context.site,
items: blog.map(post => ({
title: post.data.title,
description: post.data.description,
pubDate: post.data.pubDate,
link: `/blog/${post.slug}/`,
})),
});
}
静态表单
对于 SSG 网站,请使用第三方表单处理服务:
Formspree(最简单):
<form action="https://formspree.io/f/YOUR_FORM_ID" method="POST">
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
Netlify Forms:
<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
<input type="text" name="name" required />
<button type="submit">Send</button>
</form>
JSON-LD 结构化数据
添加 SEO 丰富摘要:
---
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: entry.data.title,
description: entry.data.description,
datePublished: entry.data.pubDate?.toISOString(),
author: { "@type": "Person", name: entry.data.author },
};
---
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
深色模式切换
<button id="theme-toggle">🌙</button>
<script>
const toggle = document.getElementById("theme-toggle");
const html = document.documentElement;
// Load saved preference or detect system preference
const savedTheme = localStorage.getItem("theme");
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
if (savedTheme === "dark" || (!savedTheme && prefersDark)) {
html.classList.add("dark");
}
toggle?.addEventListener("click", () => {
html.classList.toggle("dark");
localStorage.setItem("theme", html.classList.contains("dark") ? "dark" : "light");
});
</script>
使用 Tailwind 时,在配置中启用 darkMode: "class"。
性能最佳实践
-
局部水合:仅在需要的地方使用
client:*指令client:load- 立即水合client:idle- 浏览器空闲时水合client:visible- 进入视口时水合client:media- 媒体查询匹配时水合client:only="react"- 跳过服务端渲染,仅在客户端运行
-
图像优化:使用 Astro 的
<Image />组件 -
尽可能保持静态:岛屿架构意味着大多数内容仍为静态 HTML
-
资源指纹:在生产构建中自动生成
-
预取:在用户点击链接前自动加载
// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true, // Prefetch all links
defaultStrategy: 'viewport' // When links enter viewport
}
});
选项:'tap'(悬停/聚焦时)、'viewport'(可见时)、'load'(页面加载时)。
- 关键 CSS:使用 astro-critters 内联首屏 CSS
npm install astro-critters
import critters from 'astro-critters';
export default defineConfig({
integrations: [critters()]
});
提取关键 CSS 并将其内联,延迟加载其余 CSS,从而加快首次绘制。
部署
部署工作流
- 构建:运行
npm run build并检查dist/输出 - 预览:在 localhost:4321 使用
npm run preview测试 - 配置:在 astro.config.mjs 中设置
site、base和trailingSlash - 平台设置:初始化托管服务(例如,
firebase init hosting) - 部署:推送到平台(
firebase deploy、vercel或 git push) - 验证:检查线上 URL、测试 404 页面、确认资源已加载
快速部署命令
# Build for production
npm run build
# Preview before deploy
npm run preview
特定平台
Netlify/Vercel/Cloudflare Pages: 连接 Git 仓库——推送时自动部署。
GitHub Pages:
// astro.config.mjs
export default defineConfig({
site: 'https://username.github.io',
base: '/repo-name'
});
Firebase Hosting:
npm install -g firebase-tools
firebase login
firebase init hosting # Set public to 'dist'
npm run build
firebase deploy
firebase.json(推荐配置):
{
"hosting": {
"public": "dist",
"ignore": ["firebase.json", "**/.*"],
"cleanUrls": true,
"trailingSlash": false,
"headers": [
{
"source": "/_astro/**",
"headers": [{"key": "Cache-Control", "value": "public, max-age=31536000, immutable"}]
}
]
}
}
对齐 Astro 配置,以防止重定向循环:
// astro.config.mjs - match Firebase settings
export default defineConfig({
trailingSlash: 'never', // Must match Firebase trailingSlash: false
build: {
format: 'directory' // Default - generates /about/index.html
}
});
| Firebase 设置 | Astro 设置 | 结果 |
|---|---|---|
trailingSlash: false | trailingSlash: 'never' | /about(无斜杠) |
trailingSlash: true | trailingSlash: 'always' | /about/(带斜杠) |
| 不匹配 | 不匹配 | 重定向循环! |
常见部署注意事项
| 问题 | 解决方案 |
|---|---|
| 尾部斜杠问题 | 设置 trailingSlash: 'always' 或 'never' |
| 子路径下资源无法加载 | 在 astro.config.mjs 中配置 base |
| 404 无法正常工作 | 创建自定义 404.astro 页面 |
| 部署时构建失败 | 检查 Node 版本是否与本地一致 |
部署前检查清单
部署前,请确认:
-
npm run build执行完成且没有错误 -
npm run preview在 localhost:4321 正确显示网站 - 所有 Content Collection 架构均通过验证(
astro check) - 图片使用
<Image />组件,或放在public/中 - 所有页面均包含 SEO 元数据(标题、描述、og:*)
-
404.astro页面存在且能正确渲染 - 部署到子目录时已配置
base路径 - 已在部署平台设置环境变量
-
trailingSlash设置符合托管平台的预期 - RSS 源正常工作(
/rss.xml) - 站点地图已生成(
/sitemap-index.xml) - Lighthouse 评分 > 90
- 组件测试通过(
npm run test) - E2E 测试通过(
npm run test:e2e) - 链接检查器未发现失效链接(
npx linkinator dist)
测试与质量
静态分析
# Type checking and validation
npx astro check
# Linting (with ESLint)
npm install -D eslint
npx eslint .
# Preview production build
npm run build && npm run preview
构建时验证会通过 Content Collections 自动进行——架构错误会导致构建失败。
使用 Vitest 进行组件测试
npm install -D vitest @vitest/ui @astrojs/testing
// vitest.config.ts
import { getViteConfig } from 'astro/config';
export default getViteConfig({
test: {
include: ['src/**/*.test.ts'],
},
});
// src/components/Button.test.ts
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { expect, test } from 'vitest';
import Button from './Button.astro';
test('Button renders with text', async () => {
const container = await AstroContainer.create();
const result = await container.renderToString(Button, {
props: { text: 'Click me' }
});
expect(result).toContain('Click me');
});
使用 Playwright 进行 E2E 测试
npm install -D @playwright/test
npx playwright install
// tests/homepage.spec.ts
import { test, expect } from '@playwright/test';
test('homepage loads correctly', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My Site/);
await expect(page.locator('h1')).toBeVisible();
});
test('navigation works', async ({ page }) => {
await page.goto('/');
await page.click('a[href="/about"]');
await expect(page).toHaveURL(/about/);
});
// package.json
{
"scripts": {
"test": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
}
}
链接检查
验证内部链接没有失效:
npm install -D linkinator
npx linkinator dist --recurse
添加到 CI:
# .github/workflows/links.yml
- run: npm run build
- run: npx linkinator dist --recurse --skip "^(?!http://localhost)"
.astro 文件结构
---
// Frontmatter: JavaScript/TypeScript runs at build time
import Layout from '../layouts/Layout.astro';
import { getCollection } from 'astro:content';
const { title } = Astro.props;
const posts = await getCollection('blog');
---
<Layout title={title}>
<h1>{title}</h1>
<ul>
{posts.map(post => (
<li><a href={`/blog/${post.slug}`}>{post.data.title}</a></li>
))}
</ul>
</Layout>
<style>
/* Scoped to this component */
h1 { color: navy; }
</style>
<script>
// Client-side JavaScript
console.log('Runs in browser');
</script>
基于文件的路由
| 文件 | 路由 |
|---|---|
src/pages/index.astro | / |
src/pages/about.astro | /about |
src/pages/blog/index.astro | /blog |
src/pages/blog/[slug].astro | /blog/:slug(动态) |
src/pages/[...path].astro | 捕获所有路径 |
动态路由需要通过 getStaticPaths() 进行 SSG:
---
export function getStaticPaths() {
return [
{ params: { slug: 'post-1' } },
{ params: { slug: 'post-2' } }
];
}
---
SEO 要点
手动方式
---
const { title, description, image } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---
<head>
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
</head>
astro-seo(简化方式)
npm install astro-seo
---
import { SEO } from 'astro-seo';
---
<head>
<SEO
title="Page Title"
description="Page description"
openGraph={{
basic: {
title: "OG Title",
type: "website",
image: "/og-image.png",
}
}}
twitter={{ creator: "@handle" }}
/>
</head>
自动处理 meta 标签、Open Graph、Twitter Cards 和规范 URL。
必要集成
# Add integrations
npx astro add react # React components
npx astro add tailwind # Tailwind CSS
npx astro add mdx # MDX support
npx astro add sitemap # Auto-generate sitemap
# RSS feed
npm install @astrojs/rss
故障排除
“本地运行正常,但部署后出问题”
- 检查主机上是否设置了环境变量
- 验证
base路径配置 - 确保 Node 版本一致(建议使用 v18+)
动态路由缺少页面
- 确认
getStaticPaths()返回了所有所需路径 - 检查 params 中是否有拼写错误
Content Collection 架构错误
- 运行
astro check查看验证详情 - 确保 frontmatter 与 Zod 架构完全匹配
资源无法加载
- 对经过处理的资源使用
import - 对未经过处理的静态文件使用
public/
参考资料
特定主题的详细指南,请参阅:
references/markdown-deep-dive.md- 高级 Markdown/MDX 模式references/deployment-platforms.md- 特定平台的部署详情