Communitygithub.com

ajelinek/skills-stash

Astro TypeScriptコードの作成やリファクタリング時に使用するスキルです。静的ページやレイアウトにはAstro、インタラクティブな要素にはSolidJSまたはReactアイランド、CSS Modulesによるスタイリング、ハイドレーションディレクティブ、コンテンツコレクション、サーバーアイランド、オンデマンドレンダリング、Astro Actions、アクセシブルなセマンティックHTMLを扱います。タスクに共有UI構造、スタイリング、セマンティクス、アクセシビリティ、基礎コンポーネントの決定が含まれる場合は、`ui-engineering`と併用してください。Astroのページ、レイアウト、アイランド、アクション、またはサーバーサイドレンダリング機能の構築リクエスト時にトリガーされます。

skills-stash とは?

このスキルは、Astroフレームワークを使用してTypeScriptコードを記述またはリファクタリングするためのものです。主な目的は、静的構造(ページ、レイアウト、表示領域)にはAstro `.astro`ファイルを、すべてのインタラクティブな要素(ユーザー入力、状態管理、イベント処理)にはSolidJSまたはReactアイランド(`.tsx`)を使用するという、明確なアーキテクチャ上の決定を強制することです。スタイリングにおいては、CSS Modulesを別ファイル(`styles.module.css`)に分離することを厳格にルール化し、インラインスタイルやAstroの`<style>`ブロックの使用を禁止します。さらに、コンテンツコレクション(Astro 5の`src/content.config.ts`)、サーバーアイランド(`server:defer`)、オンデマンドレンダリング(SSR)、ミドルウェア、型安全なサーバー関数であるAstro Actions、そしてアクセシブルなセマンティックHTMLの実装に関する包括的なガイドラインを提供します。このスキルは、Claude CodeやCursorなどのAIエージェントがAstroプロジェクトの構造とコーディングパターンを理解し、一貫性のある高品質なコードを生成するのを支援するために設計されています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ajelinek/skills-stash/tree/HEAD/astro.js

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

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 vs アイランド — 中核となるアーキテクチャ上の決定

これはAstroプロジェクトにおいて最も重要な決定です。

何をするか使用するもの
データから静的なHTMLをレンダリングする — ユーザー操作なし.astro
ページシェル、レイアウトラッパー、ナビゲーション構造.astro
コンテンツセクション、ブログ投稿、マーケティング領域.astro
onClick、onChange、フォーム状態、またはリアクティブデータを持つコンポーネントSolidJS または React アイランド
基礎的なUIコンポーネント(Button、Input、Modal、Alertなど)SolidJS または React アイランド
ストアやサービスフックの読み書きを行うコンポーネントSolidJS または React アイランド

平たく言うと、ルールは次のとおりです:

  • Astroは構造とコンテンツをレンダリングします。ユーザー操作は処理しません。
  • SolidJSまたはReactは、すべてのインタラクティビティ、すべてのフォーム状態、およびすべての基本的なインタラクティブUIプリミティブ(イベントを発生させる単純なボタンでさえも)を処理します。
  • インタラクティブなUIを.astroファイルに構築しないでください。Astroにはリアクティビティがありません。.astroファイルにインタラクティビティを追加しようとすると、脆弱でテスト不可能なコードになります。

どのフレームワークを選ぶか — SolidJS それとも React?

プロジェクトは一貫して1つを使用します。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属性を決して使用しません。
  • フロントマターのフェンスの先頭でモジュールをインポートします: import styles from './styles.module.css'
  • class:list ディレクティブまたは直接のクラスバインディングを使用してクラスを適用します:
    • <div class={styles.container}>
    • <div class:list={[styles.container, isActive && styles.active]}>
  • セマンティックなクラス名を使用します。ユーティリティクラスの積み重ねは避けます。
  • すべてのデザイントークン(色、スペーシング、タイポグラフィ)にはCSSカスタムプロパティを使用します。
  • !importantは使用しません。深いセレクターは使用しません。スタイリングにIDセレクターは使用しません。

トークン戦略、テーマ変数、レスポンシブルール、アニメーションガイダンスについては、./references/styling-and-css-modules.mdを参照してください。 完全なButtonの例(別のCSS Moduleファイル付き)については、./examples/button-component/を参照してください。

プロジェクト構造

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 コレクションスキーマとローダー
├── 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 — コンポーネントディレクトリごとに1つ
  • ユーティリティ: kebab-case.ts

ディレクトリの責任範囲と命名規則の完全な内訳については、./references/project-structure.mdを参照してください。

Astroコンポーネントの形状

.astroファイルは静的構造専用です。propsとデータからHTMLをレンダリングします。イベントを処理したり、状態を管理したりしません。

  • フロントマターフェンス内で interface Props を使用して型付きのpropsを使用します。
  • Astro.props でpropsにアクセスします。
  • コンポーネントは小さく、単一目的に保ちます。
  • propsのドリルよりもコンポジションを優先します。
  • 式はシンプルに保ちます。複雑なロジックはフロントマター変数に移動します。
  • セマンティックな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>

フロントマターの規則、スロットの使用法、Astro APIリファレンスについては、./references/component-patterns.mdを参照してください。

基礎コンポーネント — 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 にします。client:load は、ユーザーがすぐに操作するUI(プライマリフォーム、ナビゲーションドロップダウン、認証ページ)にのみ使用します。

アイランドコンポーネントは常に独自の 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}>Contact Us</h2>
  <ContactForm client:visible prefillEmail={prefillEmail} />
</section>

完全な決定ガイド、フレームワーク固有の注意点、データ受け渡しルールについては、./references/islands-and-hydration.mdを参照してください。

コンテンツコレクション

コンテンツコレクションは、完全なTypeScriptの型安全性を備えてMarkdown、MDX、および構造化データファイルを管理するための標準的な方法です。

Astro 5 の主な変更点:

  • 設定ファイルは src/content.config.ts にあります(src/content/config.ts ではありません)
  • コレクションは loader を使用します。astro/loaders からの glob() または file()
  • render(entry) は astro:content からインポートされる関数です(entry.render() ではありません)
  • z は zod から直接ではなく、astro:content からインポートします
// 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 />

完全なAPI(ローダー、コレクション間参照、カスタムローダー、ランタイムコレクション、動的ルートパターン)については、./references/content-collections.mdを参照してください。

サーバーアイランド、SSR、およびアクション

サーバーアイランド (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検証エラーとハンドラーエラーを区別します。

完全なカバレッジ(レンダリングモード、アダプター設定、ミドルウェア、サーバーアイランド、オンデマンドAPI、フォームを使用したアクション、エラーコード、型付き環境変数(astro:env)、ビュートランジション)については、./references/server-rendering.mdを参照してください。

Astro API リファレンス

  • Astro.props — 型付けされたコンポーネント props
  • Astro.params — 動的ルートパラメータ([slug]、[...path])
  • Astro.request — サーバーサイドリクエストオブジェクト(ヘッダー、クッキー、URL)
  • Astro.url — 現在のページURL
  • getStaticPaths() — 静的サイト生成のためのパスを列挙する
  • getEntry() / getCollection() — コンテンツコレクションクエリ

サービス層データフロー

Astroページとレイアウトは、フロントマターフェンス内でサービス関数やリポジトリ関数を呼び出すのに適切な場所です。コンポーネントはデータをpropsとして受け取る必要があります。

  • ページのフロントマターでデータをフェッチし、コンポーネント内ではフェッチしないでください。
  • データを型付けされたpropsとして渡します。
  • コンポーネントはリポジトリや外部APIを直接呼び出してはいけません。
  • バリデーションとビジネスロジックはサービス層に保持します。

アクセシビリティ

  • 要素に意味がある場合は、セマンティックなHTML要素を使用します。
  • 実際のセマンティック情報を追加するARIA属性を追加します。
  • すべてのインタラクティブ要素にアクセシブルな名前があることを確認します。
  • 可視フォーカス状態を維持します。
  • インタラクティブ要素には最低44pxのタッチターゲットサイズを維持します。
  • prefers-reduced-motion を尊重します。
  • ページコンテンツ内で見出しを論理的に構成します(h1 → h2 → h3)。

ARIAパターン、フォームラベル、見出し階層のガイダンスについては、./references/accessibility-patterns.mdを参照してください。

サポートファイル

  • ./references/styling-and-css-modules.md CSS Modulesルール、トークン戦略、テーマ変数、レスポンシブルール、アニメーション。
  • ./references/component-patterns.md Astroフロントマターの規則、コンポーネントAPI、スロットの使用法。
  • ./references/islands-and-hydration.md アイランドの決定ガイド、ハイドレーションディレクティブ、データ受け渡し。
  • ./references/project-structure.md ディレクトリ構成と命名規則の内訳。
  • ./references/content-collections.md Astro 5 コンテンツコレクション、ローダー、スキーマ、レンダリングAPI。
  • ./references/server-rendering.md SSR、サーバーアイランド、ミドルウェア、アクション、環境変数、ビュートランジション。
  • ./references/accessibility-patterns.md ARIA、見出し階層、フォームラベル、キーボードナビゲーション。
  • ./examples/button-component/ CSS Modulesを使用したButtonコンポーネントの実装例。
  • ./examples/card-component/ Astro Cardコンポーネントとスタイリングの実装例。

関連スキル