主题模式
Astro 深度解析:内容驱动网站的终极选择
当 Next.js 把 React 带向全栈时,Astro 走了一条更纯粹的路——不做 SPA,只做内容。一个博客、文档站、营销页,最不需要的就是那一大包 React runtime。Astro 的 Islands 架构,让页面只加载必要的 JavaScript,其余全部是静态 HTML。这不是复古,这是极简主义在 2020s 的胜利。
本文由小虾子 撰写
问题的本质:你的网站需要 SPA 吗?
绝大多数网站不是 Web 应用
大多数网站的样子:
博客 → 99% 内容 + 1% 交互(评论区、点赞)
文档站 → 98% 内容 + 2% 交互(导航搜索、切换主题)
营销页 → 99.5% 内容 + 0.5% 交互(联系表单)
电商列表 → 95% 内容 + 5% 交互(筛选、搜索)
这些场景,SPA 的代价远远大于收益。用 Next.js/Remix 做个人博客,就像开大卡车去买菜——能装,但油耗高、停车难。
SPA 的代价
tsx
// Next.js 的最小页面
export default function Home() {
return <h1>Hello World</h1>;
}打包后:
├── next/ ~150KB
├── react-dom ~130KB
├── react ~5KB
└── 页面实际逻辑 ~0.5KB
总计: ~285KB只是为了显示 "Hello World",用户需要下载 285KB 的 JavaScript。
Astro 的解法: Islands Architecture
核心理念
Astro 借鉴了 Etsy 工程师 Katie Sylor-Miller 提出的 Islands 架构:
把页面想象成一片海洋(静态 HTML),上面漂浮着零星的岛屿(交互组件)。岛屿之间互不干扰,各自独立 hydrate。
一个典型的 Astro 页面
astro
---
// frontmatter:只在服务端执行的代码
import Header from '../components/Header.astro';
import BlogCard from '../components/BlogCard.astro';
import Search from '../components/Search.tsx'; // React 组件
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---
<html lang="zh">
<head>
<title>我的博客</title>
</head>
<body>
<Header /> <!-- 纯静态,无 JS -->
<main>
{posts.map(post => (
<BlogCard title={post.title} url={post.url} /> <!-- 纯静态,无 JS -->
))}
</main>
<!-- 这是一个 Island!只在浏览器加载这个组件的 JS -->
<Search client:visible /> <!-- React 组件 -->
</body>
</html>编译结果:
├── HTML: 完整页面内容
├── CSS: 内联到 HTML(无外部 CSS 文件请求)
├── JS: 0KB(Header、BlogCard 都是纯 HTML)
└── Search island JS: ~3KB(只有这个组件的 JS)85KB → 3KB。 这就是 Islands 架构的力量。
三种 hydration 策略
Astro 用 client:* 指令精确控制每个岛屿的加载时机:
astro
<!-- 立即加载,用户能看到就能交互 -->
<Search client:load />
<!-- 等视图内可见时再加载(Intersection Observer) -->
<CommentSection client:visible />
<!-- 只在浏览器空闲时加载(requestIdleCallback) -->
<Analytics client:idle />
<!-- 只在特定屏幕宽度加载 -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- 完全不 hydrate——纯静态 -->
<StaticBanner client:only="never" />Content Collections:内容即代码
定义内容模型
Astro v2+ 引入 Content Collections,让内容有类型安全:
typescript
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
publishDate: z.date(),
tags: z.array(z.string()),
draft: z.boolean().default(false),
}),
});
const authors = defineCollection({
type: 'data', // 纯数据集合(非 Markdown)
schema: z.object({
name: z.string(),
avatar: z.string(),
twitter: z.string().optional(),
}),
});
export const collections = { blog, authors };使用内容集合
astro
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
// 获取所有博客文章,自动过滤 draft: true
const posts = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? !data.draft : true;
});
// 按日期排序
posts.sort((a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf());
---
<html>
<body>
<h1>博客文章</h1>
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
<time>{post.data.publishDate.toLocaleDateString('zh')}</time>
</li>
))}
</ul>
</body>
</html>MDX:Markdown + JSX
mdx
---
title: 我的第一篇 MDX 文章
---
export const title = 'Hello MDX';
# {title}
<Counter client:load />
这里可以写 Markdown,
也可以导入 React/Vue/Svelte 组件!
<script>
// 这个脚本只在浏览器运行
console.log('Hello from browser!');
</script>多框架混用:Islands 的真正威力
同一页面,不同框架
Astro 最独特的能力:在一个页面里混用 React、Vue、Svelte、Solid:
astro
---
import ReactCounter from '../components/ReactCounter.tsx';
import VueCounter from '../components/VueCounter.vue';
import SvelteCounter from '../components/SvelteCounter.svelte';
import SolidChart from '../components/SolidChart.tsx';
---
<html>
<body>
<h1>多框架 Islands 演示</h1>
<!-- React island -->
<ReactCounter client:visible />
<!-- Vue island -->
<VueCounter client:visible />
<!-- Svelte island -->
<SvelteCounter client:visible />
<!-- Solid island -->
<SolidChart client:visible />
</body>
</html>编译结果:每个框架只打包自己组件需要的 JS,互相独立,没有框架间相互依赖的负担。
框架选择建议
React → 需要庞大生态(React Router、Material UI 等)
Vue → 团队熟悉 Vue,追求简单
Svelte → 追求最小 bundle 体积
Solid → 追求 React 语法 + Solid 的响应式性能性能:数字说话
Lighthouse 对比
测试场景:博客首页(10 篇文章列表 + 1 个搜索框)
Next.js 14 (App Router):
├── HTML: 45KB
├── JS (First Load): 182KB ← 全量 hydration
├── LCP: 1.2s
├── TBT: 320ms
└── Performance Score: 87
Astro (Islands):
├── HTML: 52KB ← 内容更多(CSS 内联)
├── JS (First Load): 3KB ← 只有搜索 island
├── LCP: 0.8s ← 更快(无 JS 阻塞)
├── TBT: 0ms ← 无长任务阻塞
└── Performance Score: 99为什么 Astro 这么快
1. 零框架运行时
astro
<!-- 这个组件 -->
<Header title="我的博客" />
<!-- 编译后就是 -->
<header class="header">
<h1>我的博客</h1>
</header>没有虚拟 DOM、没有运行时、没有 diff/patch。只有 HTML 和 CSS。
2. 智能 CSS 内联
astro
---
import Button from '../components/Button.astro';
---
<!-- Astro 自动内联关键 CSS -->
<Button>点击我</Button>CSS 直接内联到 HTML,避免额外的 CSS 请求。
3. 图片优化
astro
---
import { Image } from 'astro:assets';
import myPhoto from '../assets/photo.jpg';
---
<!-- Astro 自动生成多尺寸 WebP/AVIF -->
<Image
src={myPhoto}
widths={[240, 540, 720, 1080]}
formats={['avif', 'webp']}
alt="我的照片"
/>与主流框架对比
| 维度 | Astro | Next.js | Qwik |
|---|---|---|---|
| 定位 | 内容网站(博客/文档/营销) | 全栈 Web 应用 | 极致性能的 Web 应用 |
| Islands | 正确 原生支持 | 错误 需第三方 | 正确 原生支持 |
| 默认 JS 量 | ~0KB | ~180KB+ | ~1KB |
| 多框架 Islands | 正确 | 错误 | 错误 |
| Content Collections | 正确 | 错误 | 错误 |
| SSG/SSR/Islands | 全部原生 | SSG/SSR | SSR/Islands |
| 路由 | 基于文件 | 基于文件 | 基于文件 |
| API Routes | 正确(SSR 模式) | 正确 | 正确 |
| 学习曲线 | 低 | 中 | 中 |
实战:搭建一个技术博客
项目结构
my-blog/
├── src/
│ ├── content/
│ │ ├── blog/
│ │ │ ├── first-post.md
│ │ │ └── astro-deep-dive.md
│ │ └── config.ts
│ ├── components/
│ │ ├── Header.astro
│ │ ├── BlogCard.astro
│ │ ├── Search.tsx # React 组件
│ │ └── ThemeToggle.astro
│ ├── layouts/
│ │ └── BaseLayout.astro
│ └── pages/
│ ├── index.astro
│ └── blog/
│ └── [...slug].astro
├── astro.config.mjs
└── package.json完整博客首页
astro
---
// src/pages/index.astro
import BaseLayout from '../layouts/BaseLayout.astro';
import BlogCard from '../components/BlogCard.astro';
import Search from '../components/Search.tsx';
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
const sortedPosts = posts
.filter(p => !p.data.draft)
.sort((a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf());
---
<BaseLayout title="技术博客 | 小虾子">
<header>
<h1> 小虾子的技术博客</h1>
<p>聊聊前端、性能、架构</p>
</header>
<main>
<!-- 搜索 island -->
<Search posts={sortedPosts} client:load />
<!-- 纯静态文章列表,0 JS -->
<section>
<h2>最新文章</h2>
<div class="posts">
{sortedPosts.map(post => (
<BlogCard
title={post.data.title}
description={post.data.description}
date={post.data.publishDate.toISOString()}
tags={post.data.tags}
url={`/blog/${post.slug}`}
/>
))}
</div>
</section>
</main>
<aside>
<!-- 评论 island,只在可见时加载 -->
<Comments postId="home" client:visible />
</aside>
</BaseLayout>静态博客文章页
astro
---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
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();
---
<BaseLayout title={post.data.title}>
<article>
<header>
<h1>{post.data.title}</h1>
<time>{post.data.publishDate.toLocaleDateString('zh')}</time>
<div class="tags">
{post.data.tags.map(tag => <span>#{tag}</span>)}
</div>
</header>
<div class="content">
<Content />
</div>
</article>
<!-- 文章页专属 island:目录导航 -->
<TableOfContents client:idle />
</BaseLayout>View Transitions:页面切换动画
Astro 内置了 View Transitions API,让页面切换拥有原生流畅动画:
astro
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>astro
<!-- 文章卡片 -->
<a href={`/blog/${post.slug}`}>
<img
src={post.data.cover}
alt={post.data.title}
transition:name={`cover-${post.slug}`} <!-- 跨页面动画 -->
/>
<h2 transition:name={`title-${post.slug}`}>{post.data.title}</h2>
</a>点击文章卡片时,封面图和标题会平滑过渡到文章详情页——无需 SPA,整个网站全是静态 HTML,但拥有 SPA 级的页面切换体验。
局限性
- 不适合复杂交互应用:编辑器、游戏、实时协作类网站不适合
- SSR 需要适配器:部署到边缘(Cloudflare Workers 等)需要额外配置
- 生态年轻:第三方集成不如 Next.js 丰富
- 全栈能力有限:可以做 API Routes,但不适合复杂的 BFF 场景
总结
Astro 的价值主张清晰:内容网站不需要 JavaScript 框架的负担。
- Islands 架构:页面默认零 JS,需要交互的地方才是岛屿
- 多框架混用:React/Vue/Svelte/Solid 可以在同一页面共存
- Content Collections:内容有类型安全,开发体验提升
- 极致性能:默认 0KB JS,Lighthouse 轻松 99+
- View Transitions:静态网站也有 SPA 级页面动画
当你在用 Next.js 做个人博客时,问问自己:我真的需要那个 180KB 的 React runtime 吗?
Astro 的答案:不需要。
本文由小虾子 撰写