oneie
Version:
Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.
1,057 lines (889 loc) • 31.7 kB
Markdown
title: Frontend
dimension: knowledge
category: frontend.md
tags: ai, architecture, backend, frontend, ui
related_dimensions: events, people, things
scope: global
created: 2025-11-03
updated: 2025-11-03
version: 1.0.0
ai_context: |
This document is part of the knowledge dimension in the frontend.md category.
Location: one/knowledge/frontend.md
Purpose: Documents frontend.md - astro frontend architecture
Related dimensions: events, people, things
For AI agents: Read this to understand frontend.
# Frontend.md - Astro Frontend Architecture
## Overview
**Astro 5.14+** powers the frontend layer of the ONE Platform with server-side rendering, islands architecture, and seamless integration with React 19, Convex, and the Hono API backend. This architecture enables:
1. **Content Collections**: Type-safe content management with `astro:content` module
2. **Islands Architecture**: Static-first with selective interactivity
3. **Dual Integration**: Hono API for mutations, Convex hooks for real-time queries
4. **SSR + Edge**: Server-side rendering on Cloudflare Pages
5. **Component Library**: shadcn/ui components with Tailwind CSS v4
**Key Principle:** Frontend is **user-customizable "vibe code"** while backend (Hono + Convex) remains stable and shared across tenants.
## Architecture Vision
```
┌─────────────────────────────────────────────────────────┐
│ ASTRO FRONTEND │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Pages (.astro files) │ │
│ │ - SSR routing (file-based) │ │
│ │ - Static generation │ │
│ │ - Dynamic routes │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ Content Collections (astro:content) │ │
│ │ - Type-safe schemas with Zod │ │
│ │ - Blog, docs, marketing content │ │
│ │ - References between collections │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ React Components (Interactive Islands) │ │
│ │ - shadcn/ui components │ │
│ │ - Convex hooks (real-time data) │ │
│ │ - Hono API client (mutations) │ │
│ └────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌────────────────┴─────────────────────────────────┐ │
│ │ UI Components (shadcn/ui) │ │
│ │ - Button, Card, Dialog, etc. │ │
│ │ - Tailwind CSS v4 styling │ │
│ │ - Dark mode support │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ HTTP & WebSocket
▼
┌─────────────────────────────────────────────────────────┐
│ BACKEND INTEGRATION LAYER │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Hono API Client (REST API) │ │
│ │ /api/auth/* - Authentication │ │
│ │ /api/tokens/* - Token purchases │ │
│ │ /api/agents/* - Agent management │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Convex Hooks (Real-time Data) │ │
│ │ useQuery() - Live data subscriptions │ │
│ │ useMutation() - Optimistic updates │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Technology Stack
**Frontend Framework:**
- Astro 5.14+ (SSR, file-based routing)
- React 19 (interactive components)
- TypeScript 5.9+ (strict mode)
**UI & Styling:**
- shadcn/ui (50+ components)
- Tailwind CSS v4 (modern CSS-based config)
- Radix UI primitives
**State & Data:**
- Nanostores (lightweight state)
- Convex hooks (real-time data)
- Hono API client (mutations)
**Content:**
- `astro:content` (type-safe collections)
- Zod (schema validation)
- MDX support
**Deployment:**
- Cloudflare Pages (edge SSR)
- React 19 edge compatibility (`react-dom/server.edge`)
## Content Collections with astro:content
### 1. Collection Schema Definition
Content collections provide type-safe content management with automatic validation.
#### `src/content/config.ts`
```typescript
import { defineCollection, z, reference } from 'astro:content';
/**
* Blog collection with rich metadata
*/
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
date: z.date(),
draft: z.boolean().default(false),
image: z.string().optional(),
author: reference('authors'), // Reference to authors collection
tags: z.array(z.string()).default([]),
category: z.enum(['tutorial', 'news', 'guide', 'review', 'article']).default('article'),
readingTime: z.number().optional(),
featured: z.boolean().default(false),
relatedPosts: z.array(reference('blog')).optional(), // References to other posts
}),
});
/**
* Authors collection
*/
const authorsCollection = defineCollection({
type: 'data',
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string(),
twitter: z.string().optional(),
github: z.string().optional(),
}),
});
/**
* Docs collection with nested structure
*/
const docsCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
order: z.number().default(0),
category: z.string(),
related: z.array(reference('docs')).optional(),
}),
});
export const collections = {
blog: blogCollection,
authors: authorsCollection,
docs: docsCollection,
};
```
### 2. Querying Collections
#### Get All Entries
```typescript
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
// Get all published blog posts
const allPosts = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});
// Sort by date descending
const sortedPosts = allPosts.sort(
(a, b) => b.data.date.getTime() - a.data.date.getTime()
);
<Layout>
<h1>Blog Posts</h1>
{sortedPosts.map(post => (
<article>
<h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
<p>{post.data.description}</p>
<time>{post.data.date.toLocaleDateString()}</time>
</article>
))}
</Layout>
```
#### Get Single Entry with References
```typescript
// src/pages/blog/[...slug].astro
import { getEntry, getEntries } from 'astro:content';
const { slug } = Astro.params;
const post = await getEntry('blog', slug);
if (!post) {
return Astro.redirect('/404');
}
// Render content
const { Content, headings } = await post.render();
// Get referenced author
const author = await getEntry(post.data.author);
// Get related posts
const relatedPosts = post.data.relatedPosts
? await getEntries(post.data.relatedPosts)
: [];
<Layout title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
{/* Author info */}
<div class="author">
<img src={author.data.avatar} alt={author.data.name} />
<p>{author.data.name}</p>
</div>
{/* Rendered content */}
<Content />
{/* Related posts */}
{relatedPosts.length > 0 && (
<aside>
<h3>Related Posts</h3>
{relatedPosts.map(related => (
<a href={`/blog/${related.slug}`}>{related.data.title}</a>
))}
</aside>
)}
</article>
</Layout>
```
### 3. Dynamic Routes with Type Safety
```typescript
// src/pages/blog/[...slug].astro
import { getCollection, type CollectionEntry } from 'astro:content';
// Generate static paths
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
// Type-safe props
type Props = {
post: CollectionEntry<'blog'>;
};
const { post } = Astro.props;
const { Content, headings } = await post.render();
<Layout title={post.data.title}>
<Content />
</Layout>
```
### 4. Collection References Pattern
```typescript
// Content file: src/content/blog/my-post.md
title: "Building with Astro"
author: "john-doe" # References src/content/authors/john-doe.json
relatedPosts:
- "getting-started"
- "advanced-patterns"
tags: ["astro", "tutorial"]
Content goes here...
```
```typescript
// Author file: src/content/authors/john-doe.json
{
"name": "John Doe",
"bio": "Full-stack developer",
"avatar": "/images/john.jpg",
"twitter": "@johndoe"
}
```
### 5. Advanced Queries with Filtering
```typescript
import { getCollection } from 'astro:content';
// Filter by tag
const tag = Astro.url.searchParams.get('tag');
const posts = await getCollection('blog', ({ data }) => {
if (tag) {
return data.tags.includes(tag) && !data.draft;
}
return !data.draft;
});
// Filter by category
const category = 'tutorial';
const tutorials = await getCollection('blog', ({ data }) => {
return data.category === category && !data.draft;
});
// Featured posts only
const featured = await getCollection('blog', ({ data }) => {
return data.featured === true && !data.draft;
});
```
## React Components with Dual Integration
### Pattern: Hono API for Mutations + Convex for Queries
This dual approach combines the best of both worlds:
- **Hono API**: Business logic, authentication, payments
- **Convex Hooks**: Real-time data, live updates
### Example: Token Purchase Component
```typescript
// src/components/features/tokens/TokenPurchase.tsx
import { useState } from 'react';
import { useQuery } from 'convex/react';
import { api as honoApi } from '@/lib/api';
import { api as convexApi } from '@/convex/_generated/api';
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { useToast } from '@/hooks/use-toast';
import type { Id } from '@/convex/_generated/dataModel';
interface TokenPurchaseProps {
tokenId: Id<'entities'>;
}
/**
* Token purchase demonstrating dual integration:
* - Convex useQuery for real-time token data
* - Hono API for purchase mutation
*/
export function TokenPurchase({ tokenId }: TokenPurchaseProps) {
const { toast } = useToast();
const [loading, setLoading] = useState(false);
// Real-time token data via Convex (auto-updates!)
const token = useQuery(convexApi.queries.entities.get, { id: tokenId });
// Purchase via Hono API (handles validation, payment, etc.)
const handlePurchase = async (amount: number) => {
setLoading(true);
try {
const result = await honoApi.purchaseTokens(tokenId, amount);
if (result.error) {
throw new Error(result.error);
}
toast({
title: 'Success!',
description: `Purchased ${amount} tokens`,
});
// Convex subscription will auto-update the balance!
} catch (error: any) {
toast({
title: 'Error',
description: error.message,
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
// Loading state
if (!token) {
return (
<Card>
<CardContent className="p-6">
<div className="animate-pulse">Loading...</div>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<CardTitle>{token.name}</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="text-sm text-muted-foreground">
<p>Price: ${token.properties.price}</p>
<p>Your balance: {token.properties.balance || 0} tokens</p>
</div>
<Button
onClick={() => handlePurchase(100)}
disabled={loading}
className="w-full"
>
{loading ? 'Processing...' : 'Buy 100 Tokens'}
</Button>
</CardContent>
</Card>
);
}
```
### Example: Creator Dashboard with Real-Time Stats
```typescript
// src/components/dashboard/CreatorStats.tsx
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import type { Id } from '@/convex/_generated/dataModel';
interface CreatorStatsProps {
creatorId: Id<'entities'>;
}
/**
* Dashboard with live stats
* Pure Convex hooks - no API calls needed!
*/
export function CreatorStats({ creatorId }: CreatorStatsProps) {
// Real-time stats via Convex query
const stats = useQuery(api.queries.dashboard.getCreatorStats, { creatorId });
// Real-time content list
const content = useQuery(api.queries.content.listByCreator, { creatorId });
// Real-time revenue events
const revenue = useQuery(api.queries.events.getRevenueTotal, { creatorId });
if (!stats) {
return <div>Loading...</div>;
}
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card>
<CardHeader>
<CardTitle>Total Revenue</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">${revenue?.total || 0}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Content Pieces</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">{content?.length || 0}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Audience Members</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">{stats.audienceCount}</p>
</CardContent>
</Card>
</div>
);
}
```
### Example: Agent Creation with Hono API
```typescript
// src/components/features/agents/CreateAgent.tsx
import { useState } from 'react';
import { api } from '@/lib/api';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { useToast } from '@/hooks/use-toast';
/**
* Agent creation form
* Uses Hono API for complex business logic
*/
export function CreateAgent() {
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
name: '',
description: '',
personality: '',
model: 'gpt-4',
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
// Call Hono API for complex validation + creation
const result = await api.createAgent(formData);
if (result.error) {
throw new Error(result.error);
}
toast({
title: 'Agent Created!',
description: `${formData.name} is ready to go`,
});
// Redirect to agent page
window.location.href = `/agents/${result.id}`;
} catch (error: any) {
toast({
title: 'Error',
description: error.message,
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Label htmlFor="name">Agent Name</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
</div>
<div>
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
required
/>
</div>
<Button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create Agent'}
</Button>
</form>
);
}
```
## File Structure
```
src/
├── pages/ # File-based routing
│ ├── index.astro # Homepage
│ ├── blog/
│ │ ├── index.astro # Blog listing
│ │ └── [...slug].astro # Dynamic blog posts
│ ├── docs/
│ │ └── [...slug].astro # Documentation pages
│ ├── dashboard/
│ │ ├── index.astro # Dashboard home
│ │ ├── content.astro # Content management
│ │ ├── tokens.astro # Token management
│ │ └── agents.astro # Agent management
│ ├── auth/
│ │ ├── signin.astro # Sign in page
│ │ ├── signup.astro # Sign up page
│ │ └── reset.astro # Password reset
│ ├── rss.xml.ts # RSS feed generator
│ └── 404.astro # 404 error page
│
├── content/ # Content collections
│ ├── config.ts # Collection schemas
│ ├── blog/ # Blog posts (markdown)
│ │ ├── getting-started.md
│ │ └── advanced-patterns.md
│ ├── authors/ # Author data (JSON)
│ │ └── john-doe.json
│ └── docs/ # Documentation (markdown)
│ ├── introduction.md
│ └── api-reference.md
│
├── components/ # React components
│ ├── features/ # Feature-specific components
│ │ ├── tokens/
│ │ │ ├── TokenPurchase.tsx
│ │ │ ├── TokenBalance.tsx
│ │ │ └── TokenList.tsx
│ │ ├── agents/
│ │ │ ├── CreateAgent.tsx
│ │ │ ├── AgentCard.tsx
│ │ │ └── AgentList.tsx
│ │ ├── content/
│ │ │ ├── ContentEditor.tsx
│ │ │ ├── ContentCard.tsx
│ │ │ └── ContentList.tsx
│ │ └── dashboard/
│ │ ├── CreatorStats.tsx
│ │ ├── RevenueChart.tsx
│ │ └── ActivityFeed.tsx
│ │
│ ├── ui/ # shadcn/ui components
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── input.tsx
│ │ └── ... (50+ components)
│ │
│ ├── layout/ # Layout components
│ │ ├── Sidebar.tsx
│ │ ├── Header.tsx
│ │ └── Footer.tsx
│ │
│ └── shared/ # Shared components
│ ├── BlogSearch.tsx
│ ├── TableOfContents.tsx
│ ├── ShareButtons.tsx
│ └── ErrorBoundary.tsx
│
├── layouts/ # Astro layouts
│ ├── Layout.astro # Base layout
│ ├── Blog.astro # Blog post layout
│ └── Docs.astro # Documentation layout
│
├── lib/ # Utilities
│ ├── api.ts # Hono API client
│ ├── utils.ts # General utilities
│ └── reading-time.ts # Reading time calculator
│
├── hooks/ # React hooks
│ ├── use-toast.tsx # Toast notifications
│ ├── use-theme.tsx # Dark mode
│ └── use-auth.tsx # Authentication
│
├── stores/ # Nanostores
│ ├── layout.ts # Layout state
│ └── auth.ts # Auth state
│
├── styles/ # Global styles
│ └── global.css # Tailwind v4 config + custom CSS
│
└── config/ # Configuration
└── site.ts # Site configuration
```
## Astro Page Patterns
### Pattern 1: Static Page with Content Collection
```astro
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
import Layout from '@/layouts/Layout.astro';
import BlogSearch from '@/components/shared/BlogSearch';
const posts = await getCollection('blog', ({ data }) => !data.draft);
const sortedPosts = posts.sort((a, b) =>
b.data.date.getTime() - a.data.date.getTime()
);
<Layout title="Blog">
<div class="container py-8">
<h1 class="text-4xl font-bold mb-8">Blog</h1>
{/* Interactive search component */}
<BlogSearch client:load posts={sortedPosts} />
{/* Static list of posts */}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-8">
{sortedPosts.map(post => (
<article class="border rounded-lg p-6">
<h2 class="text-2xl font-semibold mb-2">
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
</h2>
<p class="text-muted-foreground mb-4">{post.data.description}</p>
<time class="text-sm">{post.data.date.toLocaleDateString()}</time>
</article>
))}
</div>
</div>
</Layout>
```
### Pattern 2: Dynamic Page with SSR Data
```astro
// src/pages/dashboard/index.astro
import { ConvexHttpClient } from 'convex/browser';
import { api } from '@/convex/_generated/api';
import Layout from '@/layouts/Layout.astro';
import CreatorStats from '@/components/dashboard/CreatorStats';
// SSR: Fetch user session via Hono API
const sessionRes = await fetch(`${import.meta.env.PUBLIC_API_URL}/api/auth/session`, {
headers: {
cookie: Astro.request.headers.get('cookie') || '',
},
});
const session = await sessionRes.json();
if (!session?.user) {
return Astro.redirect('/auth/signin');
}
// SSR: Fetch initial data via Convex
const convex = new ConvexHttpClient(import.meta.env.PUBLIC_CONVEX_URL);
const user = await convex.query(api.queries.entities.get, { id: session.user.id });
<Layout title="Dashboard">
<div class="container py-8">
<h1 class="text-4xl font-bold mb-8">Welcome, {user.name}!</h1>
{/* Real-time stats component */}
<CreatorStats client:load creatorId={session.user.id} />
</div>
</Layout>
```
### Pattern 3: Content Page with References
```astro
// src/pages/blog/[...slug].astro
import { getEntry, getEntries, type CollectionEntry } from 'astro:content';
import Blog from '@/layouts/Blog.astro';
import TableOfContents from '@/components/shared/TableOfContents';
import ShareButtons from '@/components/shared/ShareButtons';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
type Props = { post: CollectionEntry<'blog'> };
const { post } = Astro.props;
const { Content, headings } = await post.render();
// Get referenced author
const author = await getEntry(post.data.author);
// Get related posts
const relatedPosts = post.data.relatedPosts
? await getEntries(post.data.relatedPosts)
: [];
<Blog title={post.data.title} description={post.data.description}>
<article class="max-w-3xl mx-auto">
<header class="mb-8">
<h1 class="text-5xl font-bold mb-4">{post.data.title}</h1>
{/* Author info */}
<div class="flex items-center gap-4 mb-4">
<img src={author.data.avatar} alt={author.data.name} class="w-12 h-12 rounded-full" />
<div>
<p class="font-semibold">{author.data.name}</p>
<time>{post.data.date.toLocaleDateString()}</time>
</div>
</div>
{/* Share buttons */}
<ShareButtons client:load title={post.data.title} />
</header>
{/* Table of contents */}
<aside class="lg:fixed lg:top-24 lg:right-8 lg:w-64">
<TableOfContents client:load headings={headings} />
</aside>
{/* Rendered content */}
<div class="prose dark:prose-invert max-w-none">
<Content />
</div>
{/* Related posts */}
{relatedPosts.length > 0 && (
<aside class="mt-12 border-t pt-8">
<h3 class="text-2xl font-bold mb-4">Related Posts</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{relatedPosts.map(related => (
<a href={`/blog/${related.slug}`} class="border rounded-lg p-4 hover:bg-muted">
<h4 class="font-semibold">{related.data.title}</h4>
<p class="text-sm text-muted-foreground mt-2">{related.data.description}</p>
</a>
))}
</div>
</aside>
)}
</article>
</Blog>
```
## Integration with 6-Dimension Ontology
The frontend integrates seamlessly with the ontology through Convex hooks and Hono API:
### Entities (User-Facing Data)
```typescript
// Query entities via Convex
const token = useQuery(api.queries.entities.get, { id: tokenId });
const agent = useQuery(api.queries.entities.get, { id: agentId });
const user = useQuery(api.queries.entities.get, { id: userId });
// Create entities via Hono API
await honoApi.createAgent({ name, description });
await honoApi.createContent({ title, body });
```
### Connections (Relationships)
```typescript
// Query connections
const userTokens = useQuery(api.queries.connections.list, {
fromEntityId: userId,
relationshipType: 'holds_tokens',
});
const creatorContent = useQuery(api.queries.connections.list, {
fromEntityId: creatorId,
relationshipType: 'authored',
});
```
### Events (Activity Feed)
```typescript
// Real-time activity feed
const recentEvents = useQuery(api.queries.events.recent, {
entityId: userId,
limit: 10,
});
// Revenue events
const revenueEvents = useQuery(api.queries.events.list, {
actorId: creatorId,
eventType: 'payment_received',
});
```
### Tags (Categorization)
```typescript
// Query by tags
const taggedContent = useQuery(api.queries.tags.getEntitiesByTag, {
tagName: 'tutorial',
entityType: 'content',
});
```
## "Vibe Code" Workflow
The frontend is designed for rapid prototyping and customization:
### Step 1: Design UI Mockup
```
User sketches dashboard layout
→ Identifies data needs (revenue, content count, etc.)
```
### Step 2: Write Astro Page
```astro
// src/pages/dashboard/custom.astro
import Layout from '@/layouts/Layout.astro';
import CustomDashboard from '@/components/custom/Dashboard';
<Layout>
<CustomDashboard client:load />
</Layout>
```
### Step 3: Build React Component with Convex
```typescript
// src/components/custom/Dashboard.tsx
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';
export function CustomDashboard() {
const stats = useQuery(api.queries.dashboard.getStats);
return (
<div className="grid grid-cols-3 gap-4">
{/* Live data - no backend changes needed! */}
<Card>Revenue: ${stats?.revenue}</Card>
<Card>Content: {stats?.contentCount}</Card>
<Card>Audience: {stats?.audienceCount}</Card>
</div>
);
}
```
### Step 4: Deploy
```bash
bun run build
wrangler pages deploy dist
```
**Result:** Custom frontend deployed in minutes while backend remains stable!
## When to Use What
### Use Astro Pages For:
- Static content (blog, docs, marketing)
- SSR data fetching (initial page load)
- SEO-critical pages
- File-based routing
### Use React Components For:
- Interactive features (forms, charts, real-time updates)
- Stateful UI (modals, dropdowns, tabs)
- Convex hooks (live data)
- Reusable widgets
### Use Content Collections For:
- Blog posts
- Documentation
- Marketing pages
- Any structured content with frontmatter
### Use Convex Hooks For:
- Real-time data
- Live subscriptions
- Dashboard stats
- Activity feeds
### Use Hono API For:
- Authentication
- Complex business logic
- Payment processing
- External API integrations
## Performance Best Practices
1. **Minimize `client:load`**: Only add to truly interactive components
2. **Use Content Collections**: Type-safe, optimized content
3. **SSR for Initial Data**: Fetch critical data server-side
4. **Static Generation**: Pre-render pages when possible
5. **Image Optimization**: Use Astro's `<Image>` component
6. **Code Splitting**: React components are auto-split
7. **Convex Subscriptions**: Efficient WebSocket-based updates
## Development Commands
```bash
# Start dev server (localhost:4321)
bun run dev
# Build for production
bun run build
# Preview production build
bun run preview
# TypeScript checking
bunx astro check
# Generate content types
bunx astro sync
```
## Deployment to Cloudflare Pages
### Build Configuration
```javascript
// astro.config.mjs
export default defineConfig({
output: 'server',
adapter: cloudflare({
platformProxy: { enabled: true },
}),
vite: {
resolve: {
alias: {
'react-dom/server': 'react-dom/server.edge', // React 19 edge compatibility
},
},
},
});
```
### Deploy Commands
```bash
# Build
bun run build
# Deploy
wrangler pages deploy dist --project-name=one-platform
```
## Next Steps
1. **Read Content Collections Docs**: Understand schema patterns
2. **Explore shadcn/ui**: Learn available components
3. **Study Dual Integration**: Master Hono + Convex pattern
4. **Practice "Vibe Code"**: Build features rapidly with hooks
5. **Customize Frontend**: Make it your own!
**Result:** A high-performance, developer-friendly frontend that scales with your platform while remaining easy to customize and extend.