mcpField
P
publishing-astro-websites

publishing-astro-websites

github.com/spillwavesolutions开发3161 次安装43 周前更新
代码仓库

关于 publishing-astro-websites

这项技能介绍如何用 Astro 构建内容型静态网站,面向网站开发者。内容涵盖集合管理、搜索、文档、多语言和部署实践。

主要功能

  1. 01Astro 项目搭建与渲染模式
  2. 02Markdown 内容集合与加载器
  3. 03代码高亮与图表集成
  4. 04使用 Pagefind 或 Fuse.js 搜索
  5. 05文档、多语言与常见页面模式
  6. 06构建、部署和测试指引

适用场景

  1. 01搭建博客或文档网站
  2. 02为静态网站添加搜索
  3. 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 支持,构建快速、以内容为驱动的静态网站。

目录

快速入门

# 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)。

深色模式主题策略:

  1. CSS 变量 - 由浏览器在运行时解析颜色:
// mermaid config
mermaid.initialize({
  theme: 'base',
  themeVariables: {
    primaryColor: 'var(--diagram-primary)',
    lineColor: 'var(--diagram-line)'
  }
});
  1. Picture 元素 - 生成两种主题,并通过媒体查询切换:
<picture>
  <source srcset="/diagrams/flow-dark.svg" media="(prefers-color-scheme: dark)">
  <img src="/diagrams/flow-light.svg" alt="Flow diagram">
</picture>
  1. 内联 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 对比
功能PagefindFuse.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"。

性能最佳实践

  1. 局部水合:仅在需要的地方使用 client:* 指令

    • client:load - 立即水合
    • client:idle - 浏览器空闲时水合
    • client:visible - 进入视口时水合
    • client:media - 媒体查询匹配时水合
    • client:only="react" - 跳过服务端渲染,仅在客户端运行
  2. 图像优化:使用 Astro 的 <Image /> 组件

  3. 尽可能保持静态:岛屿架构意味着大多数内容仍为静态 HTML

  4. 资源指纹:在生产构建中自动生成

  5. 预取:在用户点击链接前自动加载

// astro.config.mjs
export default defineConfig({
  prefetch: {
    prefetchAll: true,           // Prefetch all links
    defaultStrategy: 'viewport'  // When links enter viewport
  }
});

选项:'tap'(悬停/聚焦时)、'viewport'(可见时)、'load'(页面加载时)。

  1. 关键 CSS:使用 astro-critters 内联首屏 CSS
npm install astro-critters
import critters from 'astro-critters';

export default defineConfig({
  integrations: [critters()]
});

提取关键 CSS 并将其内联,延迟加载其余 CSS,从而加快首次绘制。

部署

部署工作流
  1. 构建:运行 npm run build 并检查 dist/ 输出
  2. 预览:在 localhost:4321 使用 npm run preview 测试
  3. 配置:在 astro.config.mjs 中设置 site、base 和 trailingSlash
  4. 平台设置:初始化托管服务(例如,firebase init hosting)
  5. 部署:推送到平台(firebase deploy、vercel 或 git push)
  6. 验证:检查线上 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: falsetrailingSlash: 'never'/about(无斜杠)
trailingSlash: truetrailingSlash: '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 - 特定平台的部署详情

重要资源

评论

支持 Markdown