astro-dev
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAstro Dev
Astro Dev
Documentation Strategy
文档策略
This skill works best alongside the Astro Docs MCP (). 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.
search_astro_docs()本技能搭配Astro文档MCP()使用效果最佳。MCP负责单一概念查询(API详情、配置项),而本技能处理MCP无法覆盖的场景:生成代码前就捕获错误代码的防护规则、需要组合多个特性的多概念模式,以及帮你在不同方案间做选择的决策框架。
search_astro_docs()MCP-first workflow
MCP优先工作流
- For "how does X work?" → Use MCP:
search_astro_docs({ query: "X" }) - For "what's the right pattern for X?" → Use this skill's reference files
- Before generating any Astro code → Check the guardrails below to avoid known mistakes
- No MCP available? → Fall back to for LLM-optimized doc URLs
references/doc-endpoints.md
- 遇到「X的工作原理是什么?」类问题 → 使用MCP:
search_astro_docs({ query: "X" }) - 遇到「实现X的正确模式是什么?」类问题 → 使用本技能的参考文件
- 生成任何Astro代码前 → 查看下方的防护规则,避免已知错误
- 没有可用的MCP? → 回退到获取针对LLM优化的文档URL
references/doc-endpoints.md
Quick Router — Read the right file for your task
快速路由 — 为你的任务匹配正确的文件
| What you're doing | Read this file |
|---|---|
| Project setup / core APIs / styles / scripts / middleware | |
| Content collections (schema, loader, querying, Zod 4) | |
| Blog features (RSS, pagination, tags, SEO, TOC, Shiki) | |
| Tailwind CSS (config, theming, classes, fonts) | |
| Client directives / islands / hydration | |
| Forms, actions, data mutations | |
| View transitions, ClientRouter, script lifecycle | |
| Sessions, env vars, i18n, CSP, Cloudflare, prerender | |
| Doc URLs, MCP fallback | |
Load only the module you need. Never preload all.
| 你正在做的事 | 对应阅读文件 |
|---|---|
| 项目搭建 / 核心API / 样式 / 脚本 / 中间件 | |
| 内容集合(schema、loader、查询、Zod 4) | |
| 博客功能(RSS、分页、标签、SEO、目录、Shiki) | |
| Tailwind CSS(配置、主题、类名、字体) | |
| 客户端指令 / 孤岛 / 水合 | |
| 表单、操作、数据变更 | |
| 视图过渡、ClientRouter、脚本生命周期 | |
| 会话、环境变量、i18n、CSP、Cloudflare、预渲染 | |
| 文档URL、MCP回退 | |
仅加载你需要的模块,不要预加载全部内容。
Agent Guardrails
Agent防护规则
Patterns that agents consistently generate incorrectly. Each was identified from repeated failures.
1. Content Collections require explicit :
loaderts
// 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 . See .
image()references/content-collections.md2. 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 plugin, NOT (deprecated). See .
@tailwindcss/vite@astrojs/tailwindreferences/tailwind.md3. does not exist:
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. is a standalone function:
render()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 . Your run after integration plugins, not before.
astro:config:setupmarkdown.remarkPluginsTo 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 array — it reads the current list (which already includes the target's plugins) and prepends yours before them.
integrations[]Alternative: If the plugin is available as a rehype plugin (e.g., instead of ), use it in 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.
rehype-expressive-codeastro-expressive-codemarkdown.rehypePlugins6. Choose the right directive — both directions matter:
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 />Use for non-critical interactive components, for below-the-fold.
client:idleclient:visibleBut don't use on immediately clickable elements either:
client:idleastro
<!-- 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 . See .
client:loadreferences/islands-and-hydration.md7. 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.md8. 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.md9. Use for environment variables, not :
astro:envprocess.envts
// 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, values are inlined at build time. For runtime server env vars, use secrets or . See .
import.meta.envastro:envprocess.envreferences/server-features.md10. Styles are scoped — doesn't pass through to children:
classastro
<!-- 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 to style slotted/markdown content. See .
:global()references/astro-core-patterns.md11. is deduplicated — don't expect per-instance behavior:
<script>astro
<!-- Script runs ONCE even if component renders 10 times -->
<script>
document.querySelectorAll('.my-btn').forEach(btn => { ... })
</script>Pass server data to scripts via attributes, not template expressions. implies (no bundling). See .
data-*define:varsis:inlinereferences/astro-core-patterns.md12. in frontmatter runs at build time, not per request:
fetch()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 (). For client-side re-fetching, use a framework component with directive.
export const prerender = falseclient:*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 default to . See .
redirectToDefaultLocalefalsereferences/server-features.md14. Import Zod from , not from :
astro/zodastro:contentts
// 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 is deprecated. Always use . Astro 6 ships Zod 4 — → , → .
astro:schemaastro/zodz.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 : use , not direct calls:
<ClientRouter />astro:page-loadts
// 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 : use event delegation, not direct listeners:
<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. Preserve theme/state in , not :
astro:before-swapafter-swapts
document.addEventListener('astro:before-swap', (e) => {
e.newDocument.documentElement.setAttribute('data-theme',
localStorage.getItem('theme') || 'light')
})Setting in causes a flash — the new page renders without the attribute before your handler runs.
after-swap20. Visibility CSS () must be in global.css, not component styles:
Component loads after HTML paint → hidden content briefly visible (FOUC). Put it in so it's available on first paint.
display: none<style is:global>global.cssSee for full patterns.
references/view-transitions.md以下是Agent频繁生成错误的模式,每一条都来自反复出现的故障。
1. 内容集合需要显式声明:
loaderts
// 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.md2. 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/tailwindreferences/tailwind.md3. 已不存在:
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集成会通过前置它们的remark/rehype插件,你配置的会在集成插件之后执行,而非之前。
astro:config:setupmarkdown.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插件按照数组顺序执行,不需要额外的集成封装就能获得明确的顺序控制。在Markdown处理流程中,Remark插件始终早于Rehype插件执行。
rehype-expressive-codeastro-expressive-codemarkdown.rehypePlugins6. 选择合适的指令——双向场景都要考虑:
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:idleclient:visible但也不要在用户会立即点击的元素上使用:
client:idleastro
<!-- 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:loadreferences/islands-and-hydration.md7. 表单使用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.md8. 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.md9. 环境变量使用读取,不要用:
astro:envprocess.envts
// 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.envastro:envprocess.envreferences/server-features.md10. 样式默认隔离——不会透传到子组件:
classastro
<!-- 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>使用给插槽/Markdown内容设置样式,详见。
:global()references/astro-core-patterns.md11. 会去重——不要期望每个组件实例都单独执行:
<script>astro
<!-- Script runs ONCE even if component renders 10 times -->
<script>
document.querySelectorAll('.my-btn').forEach(btn => { ... })
</script>通过属性给脚本传递服务端数据,不要用模板表达式。会隐含(不会被打包),详见。
data-*define:varsis:inlinereferences/astro-core-patterns.md12. 前置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 = falseclient:*13. 不要手动实现多语言路由——使用Astro内置的i18n:
ts
// astro.config.ts
export default defineConfig({
i18n: {
defaultLocale: 'en',
locales: ['en', 'ko', 'ja'],
},
})注意:Astro 6将的默认值改为,详见。
redirectToDefaultLocalefalsereferences/server-features.md14. Zod从导入,不要从导入:
astro/zodastro:contentts
// 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 6内置Zod 4——改为,改为。
astro:schemaastro/zodz.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-loadts
// 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-swapafter-swapts
document.addEventListener('astro:before-swap', (e) => {
e.newDocument.documentElement.setAttribute('data-theme',
localStorage.getItem('theme') || 'light')
})在中设置会导致闪烁——新页面会先不带属性渲染,之后才执行你的处理逻辑。
after-swap20. 显隐相关CSS()必须放在global.css中,不要放在组件样式里:
组件的会在HTML绘制后加载→隐藏内容会短暂可见(FOUC)。将这类样式放在中,确保首屏绘制时就生效。
display: none<style is:global>global.css完整模式详见。
references/view-transitions.mdCommon Integration Stack
常用集成栈
See for copy-ready config files.
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'],
},
],
})可直接复制目录下的配置文件使用。
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
工作流:修改前先探查
- Check Astro version: →
package.jsonversion determines API surface"astro" - Check Node version: Astro 6 requires Node 22.12.0+
- Check config format: or
.ts(.mjsno longer supported), which integrations are installed.cjs - Check content schema: Must be (not
src/content.config.ts— errors in v6)src/content/config.ts - Check Tailwind setup: in astro config vs
@tailwindcss/vite@astrojs/tailwind - Then write code using the correct API for the detected versions
- 检查Astro版本:查看中的
package.json版本,决定可用的API范围"astro" - 检查Node版本:Astro 6要求Node 22.12.0及以上
- 检查配置格式:确认是还是
.ts(不再支持.mjs),以及安装了哪些集成.cjs - 检查内容Schema:必须是(不是
src/content.config.ts——v6会报错)src/content/config.ts - 检查Tailwind配置:是Astro配置中的,还是旧的
@tailwindcss/vite@astrojs/tailwind - 再编写代码:使用与检测到的版本匹配的正确API