Communitygithub.com

ajelinek/skills-stash

用于编写或重构 Astro TypeScript 代码:Astro 负责静态页面和布局,SolidJS 或 React 用于所有交互性,CSS Modules 位于独立文件中,以及水合指令、内容集合、服务端岛、按需渲染、Astro Actions 和可访问的语义化 HTML。当任务涉及共享 UI 结构、样式、语义、可访问性或基础组件决策时,与 `ui-engineering` 技能配合使用。在遇到构建 Astro 页面、布局、孤岛、动作或服务端渲染功能的请求时触发。

skills-stash 是什么?

该技能为使用 Astro 框架(包括 SolidJS 或 React)的开发人员提供了全面的架构决策指南。其核心原则是严格分离职责:Astro 仅负责静态结构和内容(页面、布局、纯展示区域),而所有交互性(用户输入、状态管理、事件处理)都必须由 SolidJS 或 React 孤岛组件处理。技能强制使用 CSS Modules 进行样式管理,且样式必须存放在独立的 `styles.module.css` 文件中,禁止使用内联样式或 Astro 的 `<style>` 块。该技能详细涵盖了 Astro 5 的关键特性,包括内容集合(使用 `src/content.config.ts` 和新的 `render()` API)、服务端孤岛(`server:defer` 指令)、按需渲染(SSR 模式)、中间件和类型安全的 Astro Actions。通过明确的决策表、项目结构示例和组件模式参考,该技能指导开发者避免将交互逻辑混入 Astro 文件,从而构建健壮、可维护且可测试的 Web 应用。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ajelinek/skills-stash/tree/HEAD/astro.js

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Astro

Astro 负责静态结构:页面、布局和纯展示区域。 SolidJS 或 React 负责所有交互性:每个处理用户输入、管理状态或响应事件的组件都是框架孤岛——而不是 .astro 文件。

所有组件样式均使用 CSS Modules——始终放在独立的 styles.module.css 文件中,从不内联或放在 Astro 的 <style> 块内。

何时使用此技能

  • 构建新的 Astro 项目或添加页面与布局区域
  • 构建 .astro 页面、布局或静态展示区域
  • 决定将某部分放在 Astro 中还是 SolidJS/React 孤岛中
  • 为孤岛选择合适的水合指令
  • 在 .astro 文件或框架孤岛中应用 CSS Modules 样式
  • 使用内容集合(src/content/, src/content.config.ts)
  • 将服务层数据注入服务端渲染的 Astro 页面
  • 使用服务端孤岛(server:defer)实现个性化的延迟加载区域
  • 使用适配器和中间件设置按需渲染(SSR)
  • 定义和调用 Astro Actions 以实现类型安全的服务端操作

Astro 与孤岛——核心架构决策

这是任何 Astro 项目中最关键的决策。

职责使用
从数据渲染静态 HTML——无用户交互.astro
页面外壳、布局包裹器、导航结构.astro
内容区、博客文章、营销区域.astro
任何包含 onClick、onChange、表单状态或响应式数据的组件SolidJS 或 React 孤岛
基础 UI 组件(Button, Input, Modal, Alert 等)SolidJS 或 React 孤岛
任何读写 store 或 service hook 的组件SolidJS 或 React 孤岛

简单来说,规则是:

  • Astro 渲染结构和内容,不处理用户交互。
  • SolidJS 或 React 处理所有交互性、所有表单状态以及所有基础的交互式 UI 原语——即使是一个简单的触发事件的按钮。
  • 不要在 .astro 文件中构建交互式 UI。Astro 不具备响应式能力。尝试给 .astro 文件添加交互性会产生脆弱且不可测试的代码。

选择哪个框架——SolidJS 还是 React?

项目应一致地使用其中一个。检查 astro.config.mjs 中配置的集成(@astrojs/solid-js 或 @astrojs/react),并在项目中的所有孤岛中使用该框架。不要在同一项目中混用两个框架。

阅读 ./references/islands-and-hydration.md 获取完整的决策指南和水合指令参考。

样式规则——CSS Modules 在独立文件中

这是本技能中最关键的规则。

每个 Astro 组件、页面和布局都使用 CSS Modules 实现作用域样式。CSS 应放在独立文件中——绝不要放在 Astro 的 <style> 标签内。

ComponentName/
├── index.astro         ← 组件逻辑和标记
└── styles.module.css   ← 该组件的所有样式,始终是独立文件
  • 始终 在 index.astro 旁边创建 styles.module.css。
  • 绝不 将样式放入 Astro 的 <style> 块中。
  • 绝不 使用内联 style 属性。
  • 在 frontmatter fence 顶部导入模块:import styles from './styles.module.css'
  • 使用 class:list 指令或直接的 class 绑定来应用类:
    • <div class={styles.container}>
    • <div class:list={[styles.container, isActive && styles.active]}>
  • 使用语义化的类名,不要堆叠工具类。
  • 使用 CSS 自定义属性管理所有设计令牌(颜色、间距、排版)。
  • 不使用 !important。不使用深层选择器。不使用 ID 选择器进行样式化。

阅读 ./references/styling-and-css-modules.md 获取令牌策略、主题变量、响应式规则和动画指南。 阅读 ./examples/button-component/ 获取包含独立 CSS Module 文件的完整 Button 示例。

项目结构

src/
├── components/
│   ├── foundation/         # 交互式 UI 原语——SolidJS 或 React,而非 .astro
│   │   ├── Button/
│   │   │   ├── index.tsx           ← SolidJS (.tsx) 或 React (.tsx)
│   │   │   └── styles.module.css
│   │   └── Input/
│   │       ├── index.tsx
│   │       └── styles.module.css
│   ├── layout/             # 静态布局结构——.astro
│   │   ├── Header/
│   │   │   ├── index.astro
│   │   │   └── styles.module.css
│   │   └── Footer/
│   │       ├── index.astro
│   │       └── styles.module.css
│   └── features/           # 交互式功能区域——SolidJS 或 React
│       └── SomeFeature/
│           ├── index.tsx           ← 孤岛组件
│           └── styles.module.css
├── layouts/
│   └── BaseLayout/
│       ├── index.astro             ← 布局外壳始终是 .astro
│       └── styles.module.css
├── pages/
│   ├── index/
│   │   ├── index.astro             ← 页面始终是 .astro
│   │   └── styles.module.css
│   └── [dynamic]/
│       ├── index.astro
│       └── styles.module.css
├── content/
│   ├── blog/
│   └── [collection-name]/
├── content.config.ts   ← Astro 5 集合 schemas 和 loaders
├── store/
│   ├── repository/
│   ├── service/
│   └── config.ts
├── styles/
│   ├── tokens/
│   │   ├── _colors.css
│   │   ├── _spacing.css
│   │   └── _typography.css
│   ├── global.css
│   └── themes/
│       ├── light.css
│       └── dark.css
└── utils/

命名约定:

  • .astro 页面和布局:PascalCase/ 文件夹,包含 index.astro
  • 框架孤岛组件:PascalCase/ 文件夹,包含 index.tsx
  • 页面:kebab-case/ 文件夹,包含 index.astro 或 [dynamic]/ 包含 index.astro
  • CSS Module 文件:始终命名为 styles.module.css——每个组件目录一个
  • 工具函数:kebab-case.ts

阅读 ./references/project-structure.md 获取目录职责和命名约定的完整说明。

Astro 组件结构

.astro 文件仅用于静态结构。它们从 props 和数据渲染 HTML——不处理事件或管理状态。

  • 在 frontmatter fence 中使用 interface Props 定义类型化 props。
  • 使用 Astro.props 访问 props。
  • 保持组件小巧且职责单一。
  • 优先使用组合而非 props 透传。
  • 保持表达式简单;将复杂逻辑移动到 frontmatter 变量中。
  • 使用语义化的 HTML 元素——<section>, <article>, <nav>, <main>, <header>——而不是当元素有语义含义时使用泛型 <div> 包装器。
---
import styles from './styles.module.css'

interface Props {
  title: string
  subtitle?: string
}

const { title, subtitle } = Astro.props
---

<section class={styles.hero} aria-label={title}>
  <h1 class={styles.title}>{title}</h1>
  {subtitle && <p class={styles.subtitle}>{subtitle}</p>}
  <slot />
</section>

阅读 ./references/component-patterns.md 获取 frontmatter 约定、slot 用法和 Astro API 参考。

基础组件——SolidJS 或 React,而非 Astro

基础的 UI 原语(Button, Input, Alert, Modal, Select, Toggle 等)始终 作为 SolidJS 或 React 组件构建。它们绝不是 .astro 文件。

原因: 基础组件处理用户事件、管理焦点、暴露 onClick 和 onChange 回调,有时还持有内部状态。这些在 .astro 文件中都无法实现。将“按钮”构建为 .astro 会产生静态 HTML,无法以类型化的、可重用的方式响应点击。

决策规则:

  1. 原语已存在——使用它。
  2. 原语接近但缺少一个 prop——扩展现有原语。
  3. 原语不存在——在 components/foundation/ 中将其创建为 SolidJS 或 React 组件,然后再使用。绝不要在页面或功能组件中内联一次性交互元素。

所有基础组件必须保持一致、可访问且强类型化。请参考 solid.js 或 react 技能了解孤岛组件内的实现约定。

孤岛与水合

每个交互式组件都是一个孤岛。从 .astro 页面和布局中使用 client:* 指令挂载孤岛。选择满足交互需求的最小指令。

指令使用时机
client:load页面加载后必须立即可交互
client:idle可以等到浏览器空闲时再交互
client:visible仅当进入视口时才需要水合
client:media="(query)"仅在特定视口尺寸下交互
client:only="solid-js" 或 client:only="react"需要浏览器 API 渲染;跳过 SSR

默认使用 client:visible 或 client:idle。仅对用户将立即交互的 UI(主表单、导航下拉菜单、认证页面)使用 client:load。

孤岛组件始终有自己的 styles.module.css:

components/features/ContactForm/
├── index.tsx          ← SolidJS 或 React 孤岛
└── styles.module.css  ← 独立的 CSS Module,规则与所有其他地方相同

从 Astro 页面挂载孤岛:

---
import styles from './styles.module.css'
import ContactForm from '../../components/features/ContactForm/index.tsx'

const { prefillEmail } = Astro.props
---

<section class={styles.section}>
  <h2 class={styles.heading}>联系我们</h2>
  <ContactForm client:visible prefillEmail={prefillEmail} />
</section>

阅读 ./references/islands-and-hydration.md 获取完整的决策指南、框架特定说明和数据传递规则。

内容集合

内容集合是管理 Markdown、MDX 和结构化数据文件(具备完整 TypeScript 类型安全)的规范方式。

Astro 5 关键变更:

  • 配置文件位于 src/content.config.ts(而非 src/content/config.ts)
  • 集合使用 loader——来自 astro/loaders 的 glob() 或 file()
  • render(entry) 是从 astro:content 导入的函数(而非 entry.render())
  • 从 astro:content 导入 z,而非直接从 zod 导入
// src/content.config.ts
import { defineCollection, z, reference } from 'astro:content'
import { glob } from 'astro/loaders'

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    draft: z.boolean().default(false),
  }),
})

export const collections = { blog }

在页面中渲染:

---
import { getEntry, render } from 'astro:content'
const post = await getEntry('blog', Astro.params.slug)
const { Content } = await render(post)
---
<Content />

阅读 ./references/content-collections.md 获取完整 API:loaders、跨集合引用、自定义 loaders、运行时集合和动态路由模式。

服务端孤岛、SSR 和 Actions

服务端孤岛 (server:defer)

延迟加载个性化或动态的 .astro 组件,同时立即提供页面外壳。需要适配器。

<UserCart server:defer>
  <span slot="fallback">···</span>
</UserCart>

延迟的组件在服务端运行并流式传输。用于原本会阻塞静态页面交付的用户特定 UI(购物车数量、通知、个性化问候语)。

按需渲染(SSR)

使用 export const prerender = false(混合模式)或全局设置 output: 'server' 将页面纳入服务端渲染。

SSR 页面可以访问 Astro.cookies、Astro.request、Astro.redirect() 和 Astro.locals(由中间件填充)。

中间件

在 src/middleware.ts 中定义请求拦截逻辑:

import { defineMiddleware } from 'astro:middleware'

export const onRequest = defineMiddleware(async (context, next) => {
  context.locals.user = await getUser(context.cookies.get('session')?.value)
  return next()
})

Astro Actions

类型安全的服务端函数,定义在 src/actions/index.ts 中,可从孤岛或 HTML 表单调用:

// src/actions/index.ts
import { defineAction, ActionError } from 'astro:actions'
import { z } from 'astro:content'

export const server = {
  newsletter: {
    subscribe: defineAction({
      input: z.object({ email: z.string().email() }),
      handler: async ({ email }) => {
        await addSubscriber(email)
        return { success: true }
      },
    }),
  },
}

从孤岛调用:

import { actions } from 'astro:actions'
const { data, error } = await actions.newsletter.subscribe({ email })

使用 isInputError(error) 区分 Zod 验证错误和处理程序错误。

阅读 ./references/server-rendering.md 获取完整覆盖:渲染模式、适配器配置、中间件、服务端孤岛、按需 API、带表单的 actions、错误码、类型化环境变量(astro:env)和视图过渡。

Astro API 参考

  • Astro.props ——类型化组件 props
  • Astro.params ——动态路由参数([slug], [...path])
  • Astro.request ——服务端请求对象(headers, cookies, URL)
  • Astro.url ——当前页面 URL
  • getStaticPaths() ——枚举静态站点生成的路径
  • getEntry() / getCollection() ——内容集合查询

服务层数据流

Astro 页面和布局是在 frontmatter fence 中调用服务或仓库函数的正确位置。组件应通过 props 接收数据。

  • 在页面的 frontmatter 中获取数据,而不是在组件内部。
  • 将数据作为类型化 props 向下传递。
  • 组件不得直接调用仓库或外部 API。
  • 将验证和业务逻辑保留在服务层。

可访问性

  • 在元素具有含义的地方使用语义化的 HTML 元素。
  • 在能提供真正语义信息的地方添加 ARIA 属性。
  • 确保所有交互元素都有可访问的名称。
  • 维护可见的焦点状态。
  • 保持交互元素的最小触摸目标大小为 44px。
  • 尊重 prefers-reduced-motion。
  • 在页面内容中逻辑地组织标题层级(h1 → h2 → h3)。

阅读 ./references/accessibility-patterns.md 获取 ARIA 模式、表单标签和标题层级指南。

支持文件

  • ./references/styling-and-css-modules.md CSS Modules 规则、令牌策略、主题变量、响应式规则和动画指南。
  • ./references/islands-and-hydration.md 完整的孤岛与水合决策指南、框架特定说明和数据传递规则。
  • ./references/component-patterns.md Frontmatter 约定、slot 用法和 Astro API 参考。
  • ./references/content-collections.md 内容集合 API 参考:loaders、跨集合引用、自定义 loaders 和动态路由模式。
  • ./references/server-rendering.md 渲染模式、适配器配置、中间件、服务端孤岛、按需 API、actions、错误码、类型化环境变量和视图过渡。
  • ./references/project-structure.md 目录职责和命名约定。
  • ./references/accessibility-patterns.md ARIA 模式、表单标签和标题层级指南。
  • ./examples/button-component/ 包含独立 CSS Module 文件的完整按钮示例。

相关技能