xiaohongshu
xiaohongshu
astro
Builds content-focused websites with Astro using islands architecture, content collections, and multi-framework support. Use when creating static sites, blogs, documentation, marketing pages, or content-heavy applications with minimal JavaScript.
Full skill instructions
Content-focused web framework with islands architecture for building fast static and server-rendered websites with minimal JavaScript.
Create new project:
npm create astro@latest my-site
cd my-site
npm run dev
Essential file structure:
src/
pages/ # File-based routing
index.astro # Home page (/)
about.astro # /about
blog/
[slug].astro # /blog/:slug
components/ # Reusable components
layouts/ # Page layouts
content/ # Content collections
blog/ # Blog posts collection
styles/ # Global styles
public/ # Static assets
astro.config.mjs # Astro configuration
---
// Component Script (runs at build time)
import Header from '../components/Header.astro';
import Button from '../components/Button.tsx';
interface Props {
title: string;
description?: string;
}
const { title, description = 'Default description' } = Astro.props;
// Fetch data at build time
const response = await fetch('https://api.example.com/data');
const data = await response.json();
---
<!-- Component Template -->
<html lang="en">
<head>
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<Header />
<main>
<h1>{title}</h1>
<ul>
{data.items.map((item) => (
<li>{item.name}</li>
))}
</ul>
<!-- Interactive island -->
<Button client:load>Click me</Button>
</main>
</body>
</html>
<style>
/* Scoped CSS by default */
h1 {
color: navy;
font-size: 2rem;
}
</style>
---
interface Props {
title: string;
tags: string[];
publishDate: Date;
featured?: boolean;
}
const { title, tags, publishDate, featured = false } = Astro.props;
---
<article class:list={['post', { featured }]}>
<h2>{title}</h2>
<time datetime={publishDate.toISOString()}>
{publishDate.toLocaleDateString()}
</time>
<ul>
{tags.map((tag) => <li>{tag}</li>)}
</ul>
</article>
---
// Card.astro
interface Props {
title: string;
}
const { title } = Astro.props;
---
<div class="card">
<header>
<slot name="header">{title}</slot>
</header>
<main>
<slot /> <!-- Default slot -->
</main>
<footer>
<slot name="footer">Default footer</slot>
</footer>
</div>
Using slots:
<Card title="My Card">
<h3 slot="header">Custom Header</h3>
<p>Main content goes here</p>
<button slot="footer">Action</button>
</Card>
Components are static by default. Add client:* directives for interactivity:
| Directive | When JavaScript Loads |
|---|---|
client:load | Immediately on page load |
client:idle | When browser becomes idle |
client:visible | When component enters viewport |
client:media | When media query matches |
client:only | Skip SSR, client render only |
---
import Counter from '../components/Counter.tsx';
import Newsletter from '../components/Newsletter.vue';
import Comments from '../components/Comments.svelte';
---
<!-- Load immediately (above fold, critical) -->
<Counter client:load />
<!-- Load when idle (non-critical) -->
<Newsletter client:idle />
<!-- Load when visible (below fold) -->
<Comments client:visible />
<!-- Load on mobile only -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- React-only, no SSR -->
<ReactChart client:only="react" />
# Add React
npx astro add react
# Add Vue
npx astro add vue
# Add Svelte
npx astro add svelte
# Add SolidJS
npx astro add solid
Using multiple frameworks:
---
import ReactComponent from '../components/ReactComponent.tsx';
import VueComponent from '../components/VueComponent.vue';
import SvelteComponent from '../components/SvelteComponent.svelte';
---
<ReactComponent client:load />
<VueComponent client:visible />
<SvelteComponent client:idle />
src/pages/
index.astro # /
about.astro # /about
contact.astro # /contact
blog/
index.astro # /blog
first-post.astro # /blog/first-post
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
---
// src/pages/docs/[...slug].astro
// Matches /docs, /docs/intro, /docs/guides/getting-started
export function getStaticPaths() {
return [
{ params: { slug: undefined } }, // /docs
{ params: { slug: 'intro' } }, // /docs/intro
{ params: { slug: 'guides/start' } }, // /docs/guides/start
];
}
const { slug } = Astro.params;
---
// src/content.config.ts
import { defineCollection, z } 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(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
const authors = defineCollection({
loader: glob({ pattern: '**/*.json', base: './src/content/authors' }),
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string(),
social: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
}),
}),
});
export const collections = { blog, authors };
---
import { getCollection, getEntry } from 'astro:content';
// Get all published posts
const allPosts = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});
// Sort by date
const sortedPosts = allPosts.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
// Get single entry
const featuredPost = await getEntry('blog', 'featured-post');
---
<ul>
{sortedPosts.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
</li>
))}
</ul>
---
import { getEntry } from 'astro:content';
const post = await getEntry('blog', 'my-post');
const { Content, headings } = await post.render();
---
<article>
<h1>{post.data.title}</h1>
<nav>
<h2>Table of Contents</h2>
<ul>
{headings.map((h) => (
<li>
<a href={`#${h.slug}`}>{h.text}</a>
</li>
))}
</ul>
</nav>
<Content />
</article>
---
// src/layouts/BaseLayout.astro
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" />
<meta name="description" content={description} />
<title>{title}</title>
</head>
<body>
<header>
<nav><!-- Navigation --></nav>
</header>
<main>
<slot />
</main>
<footer><!-- Footer --></footer>
</body>
</html>
Using layouts:
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Home">
<h1>Welcome!</h1>
<p>This is the home page.</p>
</BaseLayout>
---
// src/layouts/BlogPost.astro
import BaseLayout from './BaseLayout.astro';
import { type CollectionEntry } from 'astro:content';
interface Props {
post: CollectionEntry<'blog'>;
}
const { post } = Astro.props;
const { title, pubDate, heroImage } = post.data;
---
<BaseLayout title={title}>
<article>
{heroImage && <img src={heroImage} alt="" />}
<h1>{title}</h1>
<time datetime={pubDate.toISOString()}>
{pubDate.toLocaleDateString()}
</time>
<slot />
</article>
</BaseLayout>
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server', // or 'hybrid'
adapter: node({
mode: 'standalone',
}),
});
// src/pages/api/posts.json.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ request }) => {
const posts = await getPosts();
return new Response(JSON.stringify(posts), {
headers: { 'Content-Type': 'application/json' },
});
};
export const POST: APIRoute = async ({ request }) => {
const data = await request.json();
const post = await createPost(data);
return new Response(JSON.stringify(post), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
};
// astro.config.mjs
export default defineConfig({
output: 'hybrid', // Static by default, opt-in to SSR
});
---
// This page renders on each request
export const prerender = false;
const user = await getUser(Astro.cookies.get('session'));
---
<style>
/* Scoped to this component only */
h1 {
color: red;
}
</style>
<style is:global>
/* Applies globally */
body {
font-family: sans-serif;
}
</style>
---
const { color = 'blue' } = Astro.props;
---
<div class="box">Content</div>
<style define:vars={{ color }}>
.box {
background-color: var(--color);
}
</style>
npx astro add tailwind
<div class="flex items-center justify-between p-4 bg-blue-500">
<h1 class="text-2xl font-bold text-white">Hello</h1>
</div>
---
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<header transition:persist>
<!-- Persists across page navigations -->
</header>
<main transition:animate="slide">
<slot />
</main>
</body>
</html>
Custom transitions:
<div transition:name="hero" transition:animate="fade">
<img src={heroImage} alt="" />
</div>
---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.png';
---
<!-- Optimized image -->
<Image src={heroImage} alt="Hero" />
<!-- With dimensions -->
<Image src={heroImage} alt="Hero" width={800} height={600} />
<!-- Remote image -->
<Image
src="https://example.com/image.jpg"
alt="Remote"
width={400}
height={300}
/>
# .env
PUBLIC_API_URL=https://api.example.com
SECRET_KEY=abc123
---
// Server-side (secret)
const secret = import.meta.env.SECRET_KEY;
// Client-side (public)
const apiUrl = import.meta.env.PUBLIC_API_URL;
---
client:visible for below-fold contentastro:assets for automatic optimization| Mistake | Fix |
|---|---|
Adding client:* everywhere | Only for truly interactive components |
| Large client bundles | Split into smaller islands |
| Not using content collections | For blogs, docs, use collections |
| Fetching in client components | Fetch in Astro component script |
Ignoring getStaticPaths | Required for dynamic routes |
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience