Astro
Astro 是以内容为中心的 Web 框架,默认输出零 JS 的静态 HTML,通过”孤岛架构”(Islands Architecture)按需水合交互组件。支持集成 React、Vue、Svelte、Solid 等多种 UI 框架。
核心理念
传统 SPA:整页 JS bundle → 水合整个页面(大量 JS 下载)
Astro:全页静态 HTML + 孤岛(Islands)→ 仅水合交互部分
孤岛(Island):页面中独立的交互区域,每个孤岛按需加载自己的 JS,互不干扰。其余部分是纯 HTML,无运行时开销。
项目结构
my-project/
├── src/
│ ├── pages/ ← 路由(.astro / .md / .mdx / .html)
│ ├── components/ ← 组件(.astro / .svelte / .tsx 等)
│ ├── layouts/ ← 布局组件
│ ├── content/ ← 内容集合(Markdown/MDX)
│ ├── styles/ ← 全局样式
│ └── utils/ ← 工具函数
├── public/ ← 静态资源(原样复制到输出目录)
├── astro.config.mjs
└── package.json
.astro 文件语法
.astro 文件由 frontmatter(服务端 JS)和 模板(类 HTML)组成:
---
// frontmatter:仅在构建/服务端运行,不会发送到浏览器
import MyComponent from '../components/MyComponent.astro';
const title = 'Hello Astro';
const items = ['Apple', 'Banana'];
---
<!-- 模板:JSX 风格,但是 HTML -->
<html>
<head><title>{title}</title></head>
<body>
<MyComponent />
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
</body>
</html>页面与路由
静态路由
src/pages/ 下的文件自动映射为路由:
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/blog/index.astro → /blog
动态路由
---
// src/pages/posts/[slug].astro
export function getStaticPaths() {
return [
{ params: { slug: 'hello-world' } },
{ params: { slug: 'my-post' } },
];
}
const { slug } = Astro.params;
---
<h1>{slug}</h1>SSR 动态路由
开启 output: 'server' 后无需 getStaticPaths,直接读 Astro.params:
---
const { id } = Astro.params;
const data = await fetch(`/api/items/${id}`).then(r => r.json());
---Layouts(布局)
布局是带 <slot /> 的特殊组件,用于复用页面壳:
---
// src/layouts/BaseLayout.astro
const { title } = Astro.props;
---
<!DOCTYPE html>
<html>
<head><title>{title}</title></head>
<body>
<nav>导航</nav>
<slot /> <!-- 子内容插入此处 -->
<footer>页脚</footer>
</body>
</html>使用:
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="我的页面">
<h1>正文内容</h1>
</BaseLayout>具名 slot:
<!-- 布局中 -->
<slot name="head" />
<slot />
<!-- 页面中 -->
<BaseLayout>
<link slot="head" rel="stylesheet" href="/extra.css" />
<p>主内容</p>
</BaseLayout>组件集成(Islands)
通过 client:* 指令控制水合时机:
| 指令 | 时机 |
|---|---|
client:load | 页面加载立即水合 |
client:idle | 浏览器空闲时水合 |
client:visible | 组件进入视口时水合 |
client:media="(max-width: 768px)" | 媒体查询匹配时水合 |
client:only="svelte" | 仅客户端渲染,跳过 SSR |
---
import Counter from '../components/Counter.svelte';
import Chart from '../components/Chart.tsx';
---
<!-- 服务端渲染静态 HTML,不含 JS -->
<Counter />
<!-- 页面加载后立即水合为可交互组件 -->
<Counter client:load />
<!-- 滚动到此处才加载 JS -->
<Chart client:visible />数据获取
构建时(SSG)
---
// 构建时运行,结果静态化到 HTML
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---
{posts.map(p => <article>{p.title}</article>)}请求时(SSR)
---
// output: 'server' 模式下,每次请求时运行
const user = await getUser(Astro.cookies.get('session')?.value);
if (!user) return Astro.redirect('/login');
---
<p>欢迎,{user.name}</p>API 路由
src/pages/api/ 下的 .ts 文件导出 GET/POST 等处理函数:
// src/pages/api/hello.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = ({ url }) => {
const name = url.searchParams.get('name') ?? 'World';
return new Response(JSON.stringify({ message: `Hello, ${name}!` }), {
headers: { 'Content-Type': 'application/json' },
});
};内容集合(Content Collections)
对 src/content/ 下的 Markdown/MDX 文件进行类型安全的管理:
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
pubDate: z.date(),
tags: z.array(z.string()).optional(),
}),
});
export const collections = { blog };查询:
---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
---
{posts.map(p => <a href={`/blog/${p.slug}`}>{p.data.title}</a>)}集成(Integrations)
通过 astro.config.mjs 中的 integrations 字段添加:
import { defineConfig } from 'astro/config';
import svelte from '@astrojs/svelte';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
integrations: [svelte(), react(), tailwind(), sitemap()],
});常用官方集成:
| 集成 | 作用 |
|---|---|
@astrojs/svelte | Svelte 组件支持 |
@astrojs/react | React 组件支持 |
@astrojs/vue | Vue 组件支持 |
@astrojs/tailwind | Tailwind CSS |
@astrojs/sitemap | 自动生成 sitemap |
@astrojs/mdx | MDX 支持 |
@astrojs/image | 图片优化 |
中间件
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => {
const { request, cookies, redirect } = context;
// 鉴权示例
if (request.url.includes('/admin')) {
const token = cookies.get('token')?.value;
if (!token) return redirect('/login');
}
return next();
});渲染模式
| 模式 | 配置 | 适用场景 |
|---|---|---|
| SSG(默认) | output: 'static' | 博客、文档,部署到 CDN |
| SSR | output: 'server' | 用户鉴权、实时数据、个性化内容 |
| 混合 | output: 'hybrid' | 大部分静态 + 少量动态页面 |
SSR 需要适配器:
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
});Markdown 处理
Astro 内置 Markdown 支持,可通过 remark/rehype 插件扩展:
export default defineConfig({
markdown: {
remarkPlugins: [remarkMath, remarkGfm],
rehypePlugins: [rehypeKatex, rehypeSlug],
syntaxHighlight: 'shiki',
shikiConfig: { theme: 'github-dark' },
},
});本项目架构(KumaBlog)
src/pages/ ← 薄壳页面,重交互页面渲染单个 Svelte 组件
src/components/ ← Svelte 组件(全页 UI:日记、项目、留言板等)
src/layouts/
├── Layout.astro ← 基础 HTML 壳(head、主题初始化)
└── MainGridLayout.astro ← 带导航栏、Banner、TOC 的主网格布局
src/middleware.ts ← 开发环境将 /api/* 代理到 Java 后端 :9000
数据来源:
- Java 后端 API(文章、项目、留言板)→ 通过
/api/*代理/直连 - 静态 TS 数据文件(友链、fallback 项目列表)
部署:Astro 静态输出 + Nginx 反代 /api 到 Java 后端。