agent-rules-kit
Version:
CLI tool to bootstrap AI agent rules for multiple IDEs and frameworks. Generates optimized rules for Cursor, VS Code, Claude, and 6+ other AI coding assistants.
319 lines (260 loc) • 7.93 kB
Markdown
---
title: Standard Astro Architecture
description: Guidelines for implementing the standard Astro project architecture
tags: [Astro, Architecture, Standard]
globs: <root>/src/**/*.astro
---
# Standard Astro Architecture
## Overview
The standard Astro architecture follows Astro's recommended project structure with a focus on maintainability, performance, and developer experience.
## Directory Structure
```
├── public/ # Static assets
├── src/
│ ├── components/ # Reusable UI components
│ │ ├── common/ # Shared components
│ │ ├── layout/ # Layout components
│ │ └── pages/ # Page-specific components
│ ├── layouts/ # Layout templates
│ ├── pages/ # Page routes
│ ├── content/ # Content collections
│ │ └── config.ts # Collection schemas
│ ├── styles/ # Global styles and variables
│ ├── utils/ # Utility functions
│ │ ├── api.ts # API functions
│ │ └── helpers.ts # Helper functions
│ └── env.d.ts # TypeScript env definitions
├── astro.config.mjs # Astro configuration
├── package.json # Project dependencies
└── tsconfig.json # TypeScript configuration
```
## Component Organization
### Categorizing Components
In the standard architecture, components are organized into the following categories:
1. **Common Components**: Reusable UI elements like buttons, cards, etc.
2. **Layout Components**: Elements that define the page structure
3. **Page Components**: Components specific to particular pages
### Component Responsibilities
Each component should have a focused responsibility:
```typescript
// src/components/common/Button.astro
---
interface Props {
text: string;
variant?: 'primary' | 'secondary' | 'tertiary';
size?: 'small' | 'medium' | 'large';
onClick?: string;
}
const { text, variant = 'primary', size = 'medium', onClick } = Astro.props;
---
<button
class:list={['btn', `btn-${variant}`, `btn-${size}`]}
onclick={onClick}
>
{text}
</button>
<style>
.btn {
border-radius: 4px;
font-weight: 500;
cursor: pointer;
}
.btn-primary {
background-color: var(--color-primary);
color: white;
}
/* Other styles... */
</style>
```
## Layouts Implementation
Layouts are used to define consistent page structures:
```typescript
// src/layouts/BaseLayout.astro
---
import Header from '../components/layout/Header.astro';
import Footer from '../components/layout/Footer.astro';
import '../styles/global.css';
interface Props {
title: string;
description?: string;
}
const { title, description = 'My Astro Site' } = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<meta name="description" content={description} />
<title>{title}</title>
</head>
<body>
<Header />
<main>
<slot />
</main>
<Footer />
</body>
</html>
```
## Page Structure
Pages serve as the entry points for routes in your application:
```typescript
// src/pages/index.astro
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Hero from '../components/pages/home/Hero.astro';
import FeatureSection from '../components/pages/home/FeatureSection.astro';
import CTASection from '../components/common/CTASection.astro';
const pageTitle = 'Welcome to My Astro Site';
---
<BaseLayout title={pageTitle}>
<Hero />
<FeatureSection />
<CTASection title="Ready to get started?" buttonText="Get Started" href="/getting-started" />
</BaseLayout>
```
## Content Collections
Content is organized using Astro's content collections:
```typescript
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
date: z.date(),
author: z.string(),
image: z.string().optional(),
description: z.string(),
draft: z.boolean().default(false),
tags: z.array(z.string()),
}),
});
export const collections = {
blog: blogCollection,
};
```
## Data Fetching Pattern
Standard architecture follows these data fetching patterns:
### Static Data Fetching (Build Time)
```typescript
// src/pages/posts/index.astro
---
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import PostCard from '../../components/pages/blog/PostCard.astro';
// Fetch all blog posts at build time
const posts = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? !data.draft : true;
});
// Sort by date
const sortedPosts = posts.sort((a, b) =>
new Date(b.data.date).valueOf() - new Date(a.data.date).valueOf()
);
---
<BaseLayout title="Blog Posts">
<h1>Blog Posts</h1>
<div class="post-grid">
{sortedPosts.map(post => (
<PostCard post={post} />
))}
</div>
</BaseLayout>
```
### Server-Side Data Fetching (SSR)
```typescript
// src/pages/api/search.js
export async function get({ request }) {
const url = new URL(request.url);
const query = url.searchParams.get('q');
if (!query) {
return new Response(
JSON.stringify({
error: 'Missing search query',
}),
{ status: 400 }
);
}
try {
const results = await searchDatabase(query);
return new Response(JSON.stringify({ results }), { status: 200 });
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
});
}
}
```
## State Management
For client-side state, use local component state or a minimal state management library:
```typescript
// src/components/SearchForm.jsx
import { useState } from 'react';
export default function SearchForm() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
async function handleSearch(e) {
e.preventDefault();
setLoading(true);
try {
const response = await fetch(
`/api/search?q=${encodeURIComponent(query)}`
);
const data = await response.json();
setResults(data.results);
} catch (error) {
console.error('Search error:', error);
} finally {
setLoading(false);
}
}
return (
<form onSubmit={handleSearch}>
<input
type='text'
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder='Search...'
/>
<button type='submit' disabled={loading}>
{loading ? 'Searching...' : 'Search'}
</button>
{results.length > 0 && (
<ul>
{results.map((result) => (
<li key={result.id}>{result.title}</li>
))}
</ul>
)}
</form>
);
}
```
## Integration Patterns
Standard architecture supports integration with other frameworks and libraries:
```typescript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import mdx from '@astrojs/mdx';
export default defineConfig({
integrations: [react(), tailwind(), mdx()],
output: 'hybrid', // Enables SSR where needed, static by default
vite: {
ssr: {
noExternal: ['styled-components'],
},
},
});
```
## Summary of Best Practices
1. **Component Composition**: Build complex UIs from simple, reusable components
2. **Type Safety**: Use TypeScript for props and data validation
3. **Content as Data**: Leverage content collections for structured content
4. **Performance First**: Use Astro's partial hydration for minimal JavaScript
5. **Progressive Enhancement**: Build for the widest audience possible
6. **SSR Selective Usage**: Use SSR only for dynamic routes that require it