Astro 是什么?#
🙋 想象一下这样一个场景:你需要搭建一个技术博客,内容主要是 Markdown 文章,偶尔需要一些交互组件(比如代码高亮、目录导航)。用 React/Vue 全家桶?太重了,一个简单的博客页面要加载几百 KB 的 JavaScript。用纯静态 HTML?又失去了组件化开发的便利。
Astro 就是为解决这个痛点而生的。它是一个内容驱动的 Web 框架,核心理念是:
默认零 JavaScript,按需加载交互
这意味着 Astro 生成的页面默认只有 HTML 和 CSS,不会打包任何 JavaScript 到客户端。只有当你明确标记某个组件需要交互时,才会加载对应的 JS 代码。
岛屿架构(Islands Architecture)#
🎯 这是 Astro 最核心的设计理念。传统 SPA 框架把整个页面当作一个 JavaScript 应用,而 Astro 把页面看作一片”HTML 海洋”,其中散落着几个需要交互的”岛屿”。
┌─────────────────────────────────────────┐│ Header (静态 HTML) │├─────────────────────────────────────────┤│ ││ ┌──────────┐ 文章内容 ││ │ 搜索组件 │ (静态 HTML/Markdown) ││ │ (交互岛) │ ││ └──────────┘ ││ ││ ┌──────────────┐ ││ │ 评论区 │ ││ │ (交互岛) │ ││ └──────────────┘ │├─────────────────────────────────────────┤│ Footer (静态 HTML) │└─────────────────────────────────────────┘这种架构的好处是:
- ✅ 极致性能:页面首屏几乎没有 JS 阻塞
- ✅ 渐进增强:即使 JS 加载失败,静态内容仍可阅读
- ✅ 按需加载:每个岛屿独立 hydrate,不会相互阻塞
与主流框架的对比#
| 特性 | Astro | Next.js | Nuxt |
|---|---|---|---|
| 默认输出 | 静态 HTML | JS Bundle | JS Bundle |
| 客户端 JS | 按需加载 | 全量加载 | 全量加载 |
| 框架绑定 | 无(可混用) | React | Vue |
| 适用场景 | 内容站点 | Web 应用 | Web 应用 |
| 学习曲线 | 低 | 中 | 中 |
🤔 什么时候选择 Astro?
- 博客、文档、营销页面等内容为主的站点
- 对首屏性能有严格要求的场景
- 想在一个项目中混用 React、Vue、Svelte 组件
- 厌倦了配置 Webpack/Vite 的复杂构建流程
什么时候不选择 Astro?
- 需要大量实时交互的 SaaS 应用
- 单页应用(SPA)体验要求高的场景
- 团队已深度绑定某个框架生态
为什么选择 Astro?#
零 JavaScript 默认输出#
Astro 构建的页面默认不包含任何 JavaScript。这不是噱头,而是实实在在的性能收益。以当前博客为例,一篇普通文章页面的资源加载:
# 典型 Astro 博客页面index.html 15 KB (gzip)styles.css 8 KB (gzip)# JavaScript 只在需要时加载对比一个类似的 Next.js 博客:
# 典型 Next.js 博客页面index.html 5 KB_app.js 150 KB (gzip)framework.js 45 KB (gzip)page.js 30 KB (gzip)多框架组件支持#
Astro 不绑定任何 UI 框架,你可以在同一个项目中使用:
---// 同一个 Astro 页面中混用多个框架import ReactCounter from '../components/ReactCounter.jsx'import VueTimer from '../components/VueTimer.vue'import SvelteForm from '../components/SvelteForm.svelte'---
<main> <ReactCounter client:load /> <VueTimer client:idle /> <SvelteForm client:visible /></main>这在迁移老项目或团队有不同技术背景时特别有用。
内容集合与类型安全#
Astro 5 引入了 Content Layer API,为 Markdown/MDX 内容提供完整的类型支持:
import { defineCollection, z } from 'astro:content'import { glob } from 'astro/loaders'
const blog = defineCollection({ loader: glob({ base: './src/content/blog', pattern: '**/*.mdx' }), schema: z.object({ title: z.string().max(60), pubDate: z.coerce.date(), tags: z.array(z.string()).default([]), }),})定义好 Schema 后,编辑器会自动提示 Frontmatter 字段,拼写错误也会在构建时被捕获。
快速开始#
环境要求#
- Node.js: 18.17.1+ 或 20.3.0+(推荐使用 LTS 版本)
- 包管理器: npm / pnpm / yarn / bun 任选
- 编辑器: VS Code + Astro 官方扩展
# 检查 Node 版本node -v # 应该输出 v18.x 或 v20.x 以上
# 推荐使用 pnpm(当前博客使用的包管理器)npm install -g pnpm创建项目#
Astro 提供了交互式脚手架,几条命令就能创建项目:
# 使用 pnpm 创建(推荐)pnpm create astro@latest
# 或者使用 npmnpm create astro@latest
# 或者使用 yarnyarn create astro脚手架会询问几个问题:
┌ astro v5.1.0│◆ Where should we create your new project?│ ./my-astro-blog│◆ How would you like to start your new project?│ ● Use blog template (推荐初学者)│ ○ Include sample files│ ○ Empty│◆ Do you plan to write TypeScript?│ ● Yes (推荐)│ ○ No│◆ How strict should TypeScript be?│ ● Strict (推荐)│ ○ Strictest│ ○ Relaxed│◆ Install dependencies?│ ● Yes│ ○ No│◆ Initialize a new git repository?│ ● Yes│ ○ No项目结构详解#
创建完成后,你会得到这样的目录结构:
my-astro-blog/├── public/ # 静态资源(原样复制到输出目录)│ └── favicon.svg├── src/│ ├── components/ # 组件目录│ │ └── Card.astro│ ├── content/ # 内容集合目录│ │ └── blog/│ │ └── first-post.md│ ├── layouts/ # 布局组件│ │ └── Layout.astro│ ├── pages/ # 页面路由(文件即路由)│ │ └── index.astro│ ├── styles/ # 全局样式│ └── content.config.ts # 内容集合配置├── astro.config.mjs # Astro 配置├── package.json└── tsconfig.json # TypeScript 配置🎯 几个关键目录的作用:
src/pages/: 文件路由的核心,这里的每个.astro或.md文件都会生成一个页面src/content/: 存放内容集合(博客文章、文档等)src/layouts/: 页面布局模板,定义公共的页面结构src/components/: 可复用的 UI 组件public/: 静态资源,构建时原样复制到输出目录
第一个页面#
.astro 文件基本语法#
Astro 组件使用 .astro 扩展名,文件结构分为两部分:
---// 这里是「组件脚本」(Frontmatter)// 在服务端执行,不会发送到客户端import Layout from '../layouts/Layout.astro'
const title = 'Hello Astro'const items = ['苹果', '香蕉', '橙子']---
<!-- 这里是「组件模板」 --><!-- 类似 JSX,但有一些差异 --><Layout title={title}> <h1>{title}</h1> <ul> {items.map((item) => <li>{item}</li>)} </ul></Layout>
<style> /* 组件样式(默认 scoped) */ h1 { color: purple; }</style>🔶 注意: Frontmatter 使用三个短横线 --- 包裹,这和 Markdown 的 Frontmatter 语法一致,但作用不同——Astro 的 Frontmatter 是 JavaScript/TypeScript 代码。
Frontmatter 脚本区域#
Frontmatter 中可以做的事情:
---// 1. 导入组件和模块import Header from '../components/Header.astro'import { formatDate } from '../utils/date'
// 2. 获取组件 Propsinterface Props { title: string date: Date}const { title, date } = Astro.props
// 3. 获取 URL 参数const { slug } = Astro.params
// 4. 请求外部数据(在构建时执行)const response = await fetch('https://api.example.com/posts')const posts = await response.json()
// 5. 定义局部变量const formattedDate = formatDate(date)const isProduction = import.meta.env.PROD---🤔 一个常见疑问:Frontmatter 中的代码何时执行?
- 静态构建模式(默认):在
astro build时执行,每个页面只执行一次 - SSR 模式:每次请求时执行
模板表达式语法#
模板部分支持类似 JSX 的表达式:
---const name = 'Astro'const isLoggedIn = trueconst items = [1, 2, 3]---
<!-- 变量插值 --><h1>Hello, {name}!</h1>
<!-- 条件渲染 -->{isLoggedIn && <p>欢迎回来!</p>}{isLoggedIn ? <Dashboard /> : <LoginForm />}
<!-- 列表渲染 --><ul> {items.map((item) => <li>Item {item}</li>)}</ul>
<!-- 动态属性 --><div class={isLoggedIn ? 'user' : 'guest'}> <img src={`/avatars/${name}.png`} alt={name} /></div>
<!-- 展开属性 -->{/* 将对象所有属性展开为元素属性 */}<Component {...props} />🔶 与 JSX 的主要差异:
| JSX | Astro |
|---|---|
className | class |
htmlFor | for |
{/* 注释 */} | {/* 注释 */} 或 <!-- HTML注释 --> |
| 必须有根元素 | 可以有多个根元素 |
开发工作流#
常用命令#
# 启动开发服务器(热更新)pnpm dev
# 构建生产版本pnpm build
# 预览构建结果pnpm preview
# 类型检查pnpm astro check
# 同步内容集合类型pnpm astro sync热更新机制#
Astro 的开发服务器基于 Vite,提供极速的热更新体验:
- 修改
.astro文件 → 页面即时刷新 - 修改 CSS → 样式热替换(不刷新页面)
- 修改 Markdown 内容 → 页面即时刷新
- 修改
astro.config.ts→ 需要重启服务器
# 开发服务器默认运行在 4321 端口pnpm dev# Local: http://localhost:4321/TypeScript 配置#
Astro 内置 TypeScript 支持,推荐使用 strict 模式:
{ "extends": "astro/tsconfigs/strict", "compilerOptions": { "baseUrl": ".", "paths": { "~/*": ["./src/*"] // 路径别名 } }}路径别名 ~/* 让导入更简洁:
---// 不用写相对路径import Header from '~/components/Header.astro'import { SITE } from '~/config'
// 而不是// import Header from '../../components/Header.astro'---实战:理解当前博客项目结构#
让我们看看这个博客项目(基于 AntfuStyle 主题)是如何组织的。
项目目录概览#
blog/├── plugins/ # Remark/Rehype 插件├── public/ # 静态资源├── scripts/ # 构建脚本├── src/│ ├── assets/ # 图片等资源│ ├── components/ # UI 组件│ │ ├── base/ # 基础组件 (Head, Footer, Link)│ │ ├── backgrounds/ # 动画背景组件│ │ ├── nav/ # 导航组件│ │ ├── views/ # 视图组件 (渲染文章等)│ │ └── widgets/ # 小部件 (返回顶部、返回等)│ ├── content/ # 内容集合│ │ ├── blog/ # 博客文章│ │ ├── changelog/ # 更新日志│ │ ├── projects/ # 项目数据│ │ └── schema.ts # Schema 定义│ ├── layouts/ # 布局组件│ ├── pages/ # 页面路由│ ├── styles/ # 全局样式│ ├── utils/ # 工具函数│ ├── config.ts # 站点配置│ └── content.config.ts # 内容集合配置├── astro.config.ts # Astro 配置├── uno.config.ts # UnoCSS 配置└── package.json关键配置文件分析#
astro.config.ts - Astro 核心配置:
import { defineConfig } from 'astro/config'import sitemap from '@astrojs/sitemap'import unocss from 'unocss/astro'import astroExpressiveCode from 'astro-expressive-code'import mdx from '@astrojs/mdx'
export default defineConfig({ site: 'https://wangshengliang.cn/', // 站点 URL integrations: [ sitemap(), // 自动生成站点地图 unocss(), // 原子化 CSS astroExpressiveCode(), // 代码块增强 mdx(), // MDX 支持 ], markdown: { remarkPlugins, // Markdown 处理插件 rehypePlugins, },})src/config.ts - 站点配置:
export const SITE = { website: 'https://wangshengliang.cn/', title: 'wangshengliang', description: "wangshengliang's blog", author: 'wangshengliang', lang: 'zh-Hans',}
export const FEATURES = { toc: [true, { minHeadingLevel: 2, maxHeadingLevel: 4 }], giscus: [ true, { /* Giscus 评论配置 */ }, ], search: [true, { includes: ['blog', 'changelog'] }],}路径别名的使用#
项目配置了 ~/* 别名指向 src/ 目录:
// tsconfig.json{ "compilerOptions": { "paths": { "~/*": ["./src/*"] } }}这样在任何地方都可以用绝对路径导入:
---// 无论文件在哪个层级,都可以这样导入import { SITE } from '~/config'import Header from '~/components/base/Header.astro'import { formatDate } from '~/utils/date'---下一步#
🎯 恭喜你完成了 Astro 入门!现在你应该已经理解了:
- Astro 的岛屿架构理念
- 项目基本结构和文件组织
.astro文件的语法- 开发工作流和常用命令
下一篇文章,我们将深入学习 Astro 的路由系统,包括:
- 文件路由机制
- 动态路由和参数
- 分页实现
- SSR 模式下的路由