astro-dev

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Astro Dev

Astro Dev

Documentation Strategy

文档策略

This skill works best alongside the Astro Docs MCP (
search_astro_docs()
). The MCP handles single-concept lookups (API details, config options). This skill handles what MCP can't: guardrails that catch wrong code before it's generated, multi-concept patterns that require combining several features, and decision frameworks for choosing between approaches.
本技能搭配Astro文档MCP(
search_astro_docs()
)使用效果最佳
。MCP负责单一概念查询(API详情、配置项),而本技能处理MCP无法覆盖的场景:生成代码前就捕获错误代码的防护规则、需要组合多个特性的多概念模式,以及帮你在不同方案间做选择的决策框架

MCP-first workflow

MCP优先工作流

  1. For "how does X work?" → Use MCP:
    search_astro_docs({ query: "X" })
  2. For "what's the right pattern for X?" → Use this skill's reference files
  3. Before generating any Astro code → Check the guardrails below to avoid known mistakes
  4. No MCP available? → Fall back to
    references/doc-endpoints.md
    for LLM-optimized doc URLs

  1. 遇到「X的工作原理是什么?」类问题 → 使用MCP:
    search_astro_docs({ query: "X" })
  2. 遇到「实现X的正确模式是什么?」类问题 → 使用本技能的参考文件
  3. 生成任何Astro代码前 → 查看下方的防护规则,避免已知错误
  4. 没有可用的MCP? → 回退到
    references/doc-endpoints.md
    获取针对LLM优化的文档URL

Quick Router — Read the right file for your task

快速路由 — 为你的任务匹配正确的文件

What you're doingRead this file
Project setup / core APIs / styles / scripts / middleware
references/astro-core-patterns.md
Content collections (schema, loader, querying, Zod 4)
references/content-collections.md
Blog features (RSS, pagination, tags, SEO, TOC, Shiki)
references/blog-recipes.md
Tailwind CSS (config, theming, classes, fonts)
references/tailwind.md
Client directives / islands / hydration
references/islands-and-hydration.md
Forms, actions, data mutations
references/actions-and-forms.md
View transitions, ClientRouter, script lifecycle
references/view-transitions.md
Sessions, env vars, i18n, CSP, Cloudflare, prerender
references/server-features.md
Doc URLs, MCP fallback
references/doc-endpoints.md
Load only the module you need. Never preload all.

你正在做的事对应阅读文件
项目搭建 / 核心API / 样式 / 脚本 / 中间件
references/astro-core-patterns.md
内容集合(schema、loader、查询、Zod 4)
references/content-collections.md
博客功能(RSS、分页、标签、SEO、目录、Shiki)
references/blog-recipes.md
Tailwind CSS(配置、主题、类名、字体)
references/tailwind.md
客户端指令 / 孤岛 / 水合
references/islands-and-hydration.md
表单、操作、数据变更
references/actions-and-forms.md
视图过渡、ClientRouter、脚本生命周期
references/view-transitions.md
会话、环境变量、i18n、CSP、Cloudflare、预渲染
references/server-features.md
文档URL、MCP回退
references/doc-endpoints.md
仅加载你需要的模块,不要预加载全部内容。

Agent Guardrails

Agent防护规则

Patterns that agents consistently generate incorrectly. Each was identified from repeated failures.
1. Content Collections require explicit
loader
:
ts
// agents generate this (outdated)
const blog = defineCollection({ schema: z.object({...}) })

// correct pattern
import { glob } from 'astro/loaders'
const blog = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
  schema: ({ image }) => z.object({...})
})
Schema is a function receiving helpers like
image()
. See
references/content-collections.md
.
2. Tailwind uses CSS-native config, not JS:
css
/* agents generate this (outdated) */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* correct pattern */
@import "tailwindcss";
@theme inline {
  --color-primary: oklch(0.6 0.2 250);
}
Use
@tailwindcss/vite
plugin, NOT
@astrojs/tailwind
(deprecated). See
references/tailwind.md
.
3.
Astro.glob()
does not exist:
ts
// agents generate this (removed API)
const posts = await Astro.glob('./posts/*.md')

// correct pattern
import { getCollection } from 'astro:content'
const posts = await getCollection('blog')
4.
render()
is a standalone function:
ts
// agents generate this (outdated)
const { Content } = await post.render()

// correct pattern
import { render } from 'astro:content'
const { Content } = await render(post)
5. Integration plugins run before your remarkPlugins: Astro integrations prepend their remark/rehype plugins via
astro:config:setup
. Your
markdown.remarkPlugins
run after integration plugins, not before.
To run a remark plugin before an integration (e.g., intercepting code blocks before a syntax highlighter processes them), create your own Astro integration that prepends to the existing plugin list:
ts
export function myIntegration(): AstroIntegration {
  return {
    name: 'my-plugin',
    hooks: {
      'astro:config:setup': ({ config, updateConfig }) => {
        const existing = [...(config.markdown?.remarkPlugins || [])]
        updateConfig({
          markdown: { remarkPlugins: [myRemarkPlugin, ...existing] },
        })
      },
    },
  }
}
Place it after the target integration in the
integrations[]
array — it reads the current list (which already includes the target's plugins) and prepends yours before them.
Alternative: If the plugin is available as a rehype plugin (e.g.,
rehype-expressive-code
instead of
astro-expressive-code
), use it in
markdown.rehypePlugins
directly. Rehype plugins execute in array order, giving you explicit control without the integration wrapper trick. Remark plugins always run before rehype plugins in the markdown pipeline.
6. Choose the right
client:
directive — both directions matter:
astro
<!-- agents do this (wasteful) -->
<Counter client:load />
<Sidebar client:load />
<Footer client:load />

<!-- correct: choose based on urgency -->
<Counter client:load />
<Sidebar client:idle />
<Footer client:visible />
Use
client:idle
for non-critical interactive components,
client:visible
for below-the-fold.
But don't use
client:idle
on immediately clickable elements either:
astro
<!-- WRONG: user clicks before hydration, click is silently lost -->
<SearchButton client:idle />
<MobileMenu client:idle />

<!-- correct: elements users click immediately need client:load -->
<SearchButton client:load />
<MobileMenu client:load />
If a user can click it in the first 2 seconds, it must be
client:load
. See
references/islands-and-hydration.md
.
7. Use Actions for forms, not manual API routes:
ts
// agents build this (verbose, no validation)
// src/pages/api/subscribe.ts
export const POST: APIRoute = async ({ request }) => { ... }

// correct: use Actions (typed, validated, CSRF-protected)
// src/actions/index.ts
export const server = {
  subscribe: defineAction({
    accept: 'form',
    input: z.object({ email: z.email() }),  // Zod 4: z.email(), not z.string().email()
    handler: async (input) => { ... },
  }),
}
See
references/actions-and-forms.md
.
8. Cookies, sessions, and forms require on-demand rendering:
astro
---
// agents forget this — the page silently fails or behaves unexpectedly
export const prerender = false  // REQUIRED for dynamic features

const session = Astro.cookies.get('session')
---
Pages are prerendered by default. Any page using cookies, sessions, Actions, or POST handling must opt out. See
references/server-features.md
.
9. Use
astro:env
for environment variables, not
process.env
:
ts
// agents do this (unvalidated, no type safety)
const secret = process.env.API_KEY

// correct: define schema in config, import from virtual module
import { API_KEY } from 'astro:env/server'
Note: In Astro 6,
import.meta.env
values are inlined at build time. For runtime server env vars, use
astro:env
secrets or
process.env
. See
references/server-features.md
.
10. Styles are scoped —
class
doesn't pass through to children:
astro
<!-- agents assume class passes through (it doesn't) -->
<Card class="mt-4" />

<!-- correct: Card.astro must accept and apply class -->
---
const { class: className, ...rest } = Astro.props
---
<div class:list={['card', className]} {...rest}>
  <slot />
</div>
Use
:global()
to style slotted/markdown content. See
references/astro-core-patterns.md
.
11.
<script>
is deduplicated — don't expect per-instance behavior:
astro
<!-- Script runs ONCE even if component renders 10 times -->
<script>
  document.querySelectorAll('.my-btn').forEach(btn => { ... })
</script>
Pass server data to scripts via
data-*
attributes, not template expressions.
define:vars
implies
is:inline
(no bundling). See
references/astro-core-patterns.md
.
12.
fetch()
in frontmatter runs at build time, not per request:
astro
---
// In static mode, this runs ONCE at build time
const data = await fetch('https://api.example.com/data').then(r => r.json())
---
For per-request data, page must be on-demand (
export const prerender = false
). For client-side re-fetching, use a framework component with
client:*
directive.
13. Don't build manual locale routing — use Astro's built-in i18n:
ts
// astro.config.ts
export default defineConfig({
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'ko', 'ja'],
  },
})
Note: Astro 6 changed
redirectToDefaultLocale
default to
false
. See
references/server-features.md
.
14. Import Zod from
astro/zod
, not from
astro:content
:
ts
// agents generate this (deprecated in Astro 6)
import { defineCollection, z } from 'astro:content'

// correct pattern
import { defineCollection } from 'astro:content'
import { z } from 'astro/zod'
Also
astro:schema
is deprecated. Always use
astro/zod
. Astro 6 ships Zod 4 —
z.string().email()
z.email()
,
{message:}
{error:}
.
15. Legacy content collections are fully removed in Astro 6:
ts
// ERRORS in Astro 6:
// - src/content/config.ts (must be src/content.config.ts)
// - defineCollection({ type: 'content' }) (type field removed)
// - defineCollection({}) without loader (loader is mandatory)

// correct: every collection needs a loader
import { defineCollection } from 'astro:content'
import { glob } from 'astro/loaders'

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
})
16. CJS config files are no longer supported:
ts
// ERRORS in Astro 6
// astro.config.cjs — CommonJS not supported
// module.exports = { ... }

// correct: use ESM (.ts or .mjs)
// astro.config.ts
import { defineConfig } from 'astro/config'
export default defineConfig({ ... })
17. With
<ClientRouter />
: use
astro:page-load
, not direct calls:
ts
// breaks on first load or after navigation
initFeature()
document.addEventListener('astro:after-swap', initFeature)

// correct: covers both initial load AND navigations
document.addEventListener('astro:page-load', initFeature)
18. With
<ClientRouter />
: use event delegation, not direct listeners:
ts
// listeners lost when DOM is swapped during navigation
btn.addEventListener('click', handler)

// correct: survives DOM swaps
document.addEventListener('click', (e) => {
  if ((e.target as HTMLElement).closest('.btn')) handler()
})
19. Preserve theme/state in
astro:before-swap
, not
after-swap
:
ts
document.addEventListener('astro:before-swap', (e) => {
  e.newDocument.documentElement.setAttribute('data-theme',
    localStorage.getItem('theme') || 'light')
})
Setting in
after-swap
causes a flash — the new page renders without the attribute before your handler runs.
20. Visibility CSS (
display: none
) must be in global.css, not component styles:
Component
<style is:global>
loads after HTML paint → hidden content briefly visible (FOUC). Put it in
global.css
so it's available on first paint.
See
references/view-transitions.md
for full patterns.

以下是Agent频繁生成错误的模式,每一条都来自反复出现的故障。
1. 内容集合需要显式声明
loader
ts
// agents generate this (outdated)
const blog = defineCollection({ schema: z.object({...}) })

// correct pattern
import { glob } from 'astro/loaders'
const blog = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
  schema: ({ image }) => z.object({...})
})
Schema是接收
image()
这类辅助工具的函数,详见
references/content-collections.md
2. Tailwind使用CSS原生配置,而非JS:
css
/* agents generate this (outdated) */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* correct pattern */
@import "tailwindcss";
@theme inline {
  --color-primary: oklch(0.6 0.2 250);
}
使用
@tailwindcss/vite
插件,不要使用已废弃的
@astrojs/tailwind
,详见
references/tailwind.md
3.
Astro.glob()
已不存在:
ts
// agents generate this (removed API)
const posts = await Astro.glob('./posts/*.md')

// correct pattern
import { getCollection } from 'astro:content'
const posts = await getCollection('blog')
4.
render()
是独立函数:
ts
// agents generate this (outdated)
const { Content } = await post.render()

// correct pattern
import { render } from 'astro:content'
const { Content } = await render(post)
5. 集成插件的执行顺序早于你自定义的remarkPlugins: Astro集成会通过
astro:config:setup
前置它们的remark/rehype插件,你配置的
markdown.remarkPlugins
会在集成插件之后执行,而非之前。
如果你需要让某个remark插件在集成之前执行(例如在语法高亮器处理代码块之前拦截内容),可以创建自定义Astro集成,将你的插件前置到现有插件列表中:
ts
export function myIntegration(): AstroIntegration {
  return {
    name: 'my-plugin',
    hooks: {
      'astro:config:setup': ({ config, updateConfig }) => {
        const existing = [...(config.markdown?.remarkPlugins || [])]
        updateConfig({
          markdown: { remarkPlugins: [myRemarkPlugin, ...existing] },
        })
      },
    },
  }
}
将它放在
integrations[]
数组中目标集成的后面——它会读取已经包含目标集成插件的当前列表,将你的插件前置到它们前面。
替代方案: 如果该插件有rehype版本(例如用
rehype-expressive-code
替代
astro-expressive-code
),可以直接在
markdown.rehypePlugins
中使用。Rehype插件按照数组顺序执行,不需要额外的集成封装就能获得明确的顺序控制。在Markdown处理流程中,Remark插件始终早于Rehype插件执行。
6. 选择合适的
client:
指令——双向场景都要考虑:
astro
<!-- agents do this (wasteful) -->
<Counter client:load />
<Sidebar client:load />
<Footer client:load />

<!-- correct: choose based on urgency -->
<Counter client:load />
<Sidebar client:idle />
<Footer client:visible />
非核心交互组件使用
client:idle
,首屏以下的组件使用
client:visible
但也不要在用户会立即点击的元素上使用
client:idle
astro
<!-- WRONG: user clicks before hydration, click is silently lost -->
<SearchButton client:idle />
<MobileMenu client:idle />

<!-- correct: elements users click immediately need client:load -->
<SearchButton client:load />
<MobileMenu client:load />
如果用户可能在页面加载前2秒点击该元素,必须使用
client:load
,详见
references/islands-and-hydration.md
7. 表单使用Actions实现,不要手动写API路由:
ts
// agents build this (verbose, no validation)
// src/pages/api/subscribe.ts
export const POST: APIRoute = async ({ request }) => { ... }

// correct: use Actions (typed, validated, CSRF-protected)
// src/actions/index.ts
export const server = {
  subscribe: defineAction({
    accept: 'form',
    input: z.object({ email: z.email() }),  // Zod 4: z.email(), not z.string().email()
    handler: async (input) => { ... },
  }),
}
详见
references/actions-and-forms.md
8. Cookie、会话和表单需要按需渲染:
astro
---
// agents forget this — the page silently fails or behaves unexpectedly
export const prerender = false  // 动态功能必填项

const session = Astro.cookies.get('session')
---
页面默认开启预渲染,任何使用Cookie、会话、Actions或POST处理的页面都必须手动关闭预渲染,详见
references/server-features.md
9. 环境变量使用
astro:env
读取,不要用
process.env
ts
// agents do this (unvalidated, no type safety)
const secret = process.env.API_KEY

// correct: define schema in config, import from virtual module
import { API_KEY } from 'astro:env/server'
注意:在Astro 6中,
import.meta.env
的值会在构建时内联。运行时服务端环境变量请使用
astro:env
密钥或
process.env
,详见
references/server-features.md
10. 样式默认隔离——
class
不会透传到子组件:
astro
<!-- agents assume class passes through (it doesn't) -->
<Card class="mt-4" />

<!-- correct: Card.astro must accept and apply class -->
---
const { class: className, ...rest } = Astro.props
---
<div class:list={['card', className]} {...rest}>
  <slot />
</div>
使用
:global()
给插槽/Markdown内容设置样式,详见
references/astro-core-patterns.md
11.
<script>
会去重——不要期望每个组件实例都单独执行:
astro
<!-- Script runs ONCE even if component renders 10 times -->
<script>
  document.querySelectorAll('.my-btn').forEach(btn => { ... })
</script>
通过
data-*
属性给脚本传递服务端数据,不要用模板表达式。
define:vars
会隐含
is:inline
(不会被打包),详见
references/astro-core-patterns.md
12. 前置matter中的
fetch()
在构建时执行,而非每次请求时执行:
astro
---
// In static mode, this runs ONCE at build time
const data = await fetch('https://api.example.com/data').then(r => r.json())
---
如果需要每次请求都拉取数据,页面必须开启按需渲染(
export const prerender = false
)。客户端重新拉取请使用带
client:*
指令的框架组件。
13. 不要手动实现多语言路由——使用Astro内置的i18n:
ts
// astro.config.ts
export default defineConfig({
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'ko', 'ja'],
  },
})
注意:Astro 6将
redirectToDefaultLocale
的默认值改为
false
,详见
references/server-features.md
14. Zod从
astro/zod
导入,不要从
astro:content
导入:
ts
// agents generate this (deprecated in Astro 6)
import { defineCollection, z } from 'astro:content'

// correct pattern
import { defineCollection } from 'astro:content'
import { z } from 'astro/zod'
同时
astro:schema
已废弃,始终使用
astro/zod
。Astro 6内置Zod 4——
z.string().email()
改为
z.email()
{message:}
改为
{error:}
15. 旧版内容集合在Astro 6中已完全移除:
ts
// ERRORS in Astro 6:
// - src/content/config.ts (must be src/content.config.ts)
// - defineCollection({ type: 'content' }) (type field removed)
// - defineCollection({}) without loader (loader is mandatory)

// correct: every collection needs a loader
import { defineCollection } from 'astro:content'
import { glob } from 'astro/loaders'

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
})
16. 不再支持CJS配置文件:
ts
// ERRORS in Astro 6
// astro.config.cjs — CommonJS not supported
// module.exports = { ... }

// correct: use ESM (.ts or .mjs)
// astro.config.ts
import { defineConfig } from 'astro/config'
export default defineConfig({ ... })
17. 使用
<ClientRouter />
时:监听
astro:page-load
事件,不要直接调用初始化方法:
ts
// breaks on first load or after navigation
initFeature()
document.addEventListener('astro:after-swap', initFeature)

// correct: covers both initial load AND navigations
document.addEventListener('astro:page-load', initFeature)
18. 使用
<ClientRouter />
时:使用事件委托,不要直接绑定监听器:
ts
// listeners lost when DOM is swapped during navigation
btn.addEventListener('click', handler)

// correct: survives DOM swaps
document.addEventListener('click', (e) => {
  if ((e.target as HTMLElement).closest('.btn')) handler()
})
19. 在
astro:before-swap
事件中保留主题/状态,不要在
after-swap
中处理:
ts
document.addEventListener('astro:before-swap', (e) => {
  e.newDocument.documentElement.setAttribute('data-theme',
    localStorage.getItem('theme') || 'light')
})
after-swap
中设置会导致闪烁——新页面会先不带属性渲染,之后才执行你的处理逻辑。
20. 显隐相关CSS(
display: none
)必须放在global.css中,不要放在组件样式里:
组件的
<style is:global>
会在HTML绘制后加载→隐藏内容会短暂可见(FOUC)。将这类样式放在
global.css
中,确保首屏绘制时就生效。
完整模式详见
references/view-transitions.md

Common Integration Stack

常用集成栈

See
templates/
for copy-ready config files.
ts
// astro.config.ts
import { defineConfig, fontProviders } from 'astro/config'
import tailwindcss from '@tailwindcss/vite'
import mdx from '@astrojs/mdx'
import react from '@astrojs/react'
import sitemap from '@astrojs/sitemap'

export default defineConfig({
  site: 'https://example.com',
  integrations: [mdx(), react(), sitemap()],
  vite: {
    plugins: [tailwindcss()],
  },
  fonts: [
    {
      provider: fontProviders.google(),
      name: 'Inter',
      cssVariable: '--font-inter',
      weights: ['100 900'],
    },
  ],
})

可直接复制
templates/
目录下的配置文件使用。
ts
// astro.config.ts
import { defineConfig, fontProviders } from 'astro/config'
import tailwindcss from '@tailwindcss/vite'
import mdx from '@astrojs/mdx'
import react from '@astrojs/react'
import sitemap from '@astrojs/sitemap'

export default defineConfig({
  site: 'https://example.com',
  integrations: [mdx(), react(), sitemap()],
  vite: {
    plugins: [tailwindcss()],
  },
  fonts: [
    {
      provider: fontProviders.google(),
      name: 'Inter',
      cssVariable: '--font-inter',
      weights: ['100 900'],
    },
  ],
})

Workflow: Explore Before Modifying

工作流:修改前先探查

  1. Check Astro version:
    package.json
    "astro"
    version determines API surface
  2. Check Node version: Astro 6 requires Node 22.12.0+
  3. Check config format:
    .ts
    or
    .mjs
    (
    .cjs
    no longer supported), which integrations are installed
  4. Check content schema: Must be
    src/content.config.ts
    (not
    src/content/config.ts
    — errors in v6)
  5. Check Tailwind setup:
    @tailwindcss/vite
    in astro config vs
    @astrojs/tailwind
  6. Then write code using the correct API for the detected versions
  1. 检查Astro版本:查看
    package.json
    中的
    "astro"
    版本,决定可用的API范围
  2. 检查Node版本:Astro 6要求Node 22.12.0及以上
  3. 检查配置格式:确认是
    .ts
    还是
    .mjs
    (不再支持
    .cjs
    ),以及安装了哪些集成
  4. 检查内容Schema:必须是
    src/content.config.ts
    (不是
    src/content/config.ts
    ——v6会报错)
  5. 检查Tailwind配置:是Astro配置中的
    @tailwindcss/vite
    ,还是旧的
    @astrojs/tailwind
  6. 再编写代码:使用与检测到的版本匹配的正确API