健兼
astro博客搭建教程
jianzhang

了解Astro

Astro 是最适合构建像博客、营销网站、电子商务网站这样的以内容驱动的网站的 Web 框架。

Astro 特性

  • 群岛:一种基于组件的针对内容驱动的网站进行优化的 Web 架构。
  • UI 无关:支持 React、Preact、Svelte、Vue、Solid、Lit、HTMX、Web 组件等等。
  • 服务器优先:将沉重的渲染移出访问者的设备。
  • 默认无 JS:让客户端更少的执行 JS ,以提升网站速度。
  • 内容集合:为你的 Markdown 内容,提供组织、校验并保证 TypeScript 类型安全。
  • 可定制:Tailwind、MDX 和数百个集成可供选择。

基本概念

项目结构

Astro 为你的项目提供了一个有想法的文件夹布局。每个 Astro 项目的根目录下都应该包括以下目录和文件:

  • src/* - 你的项目源代码(组件、页面、样式等)。
  • public/* - 你的非代码、未处理的资源(字体、图标等)。
  • package.json - 项目清单。
  • astro.config.mjs - Astro 配置文件(推荐)。
  • tsconfig.json - TypeScript 配置文件(推荐)。

组件

Astro 组件,通过文件扩展名 .astro 来识别 Astro 组件,放在 src/components/ 文件夹下。Astro 组件最重要的一点是它们不会在客户端上渲染。它们在构建时或使用 服务器端渲染(SSR) 按需呈现为 HTML。 Astro 组件是由两个主要部分所组成的——组件 script 和组件模板。

1
---
2
// 组件脚本(JavaScript)
3
---
4
<!-- 组件模板(HTML + JS 表达式)-->

Astro 组件可以定义和接受参数。可以在 frontmatter script 中的 Astro.props 中使用。 定义:

1
---
2
// 使用:<GreetingHeadline greeting="你好" name="朋友" />
3
const { greeting, name } = Astro.props
4
---
5
<h2>{greeting}{name}!</h2>

使用:

1
---
2
import GreetingHeadline from './GreetingHeadline.astro';
3
const name = "Astro";
4
---
5
<h1>Greeting Card</h1>
6
<GreetingHeadline greeting="嗨" name={name} />
7
<p>希望你有美好的一天!</p>

插槽 <slot /> 元素是嵌入外部 HTML 内容的占位符,你可以将其他文件中的子元素注入(或“嵌入”)到组件模板中。

当你在 Astro 组件内置了 <style> 标签时,Astro 就会自动检测 CSS 并开始为你处理样式。 Astro <style> 标签内的 CSS 规则默认自动限定范围。作用域样式在幕后编译,只适用于写在同一组件内的 HTML。 源码:

1
<style>
2
h1 {
3
color: red;
4
}
5
6
.text {
7
color: blue;
8
}
9
</style>

会编译成:

1
<style>
2
h1[data-astro-cid-hhnqfkh6] {
3
color: red;
4
}
5
6
.text[data-astro-cid-hhnqfkh6] {
7
color: blue;
8
}
9
</style>

:::notice 你可以通过 <style is:global> 属性选择不自动限定 CSS 范围。 你也可以在 <style> 使用 :global() 包裹css选择器,这可以将 CSS 样式应用于子组件。 :::

页面

页面是位于 Astro 项目的 src/pages/ 子目录中的文件。它们负责处理路由、数据加载以及网站中每个页面的整体页面布局。 Astro 支持 src/pages/ 目录中的以下文件类型:

  • .astro
  • .md
  • .mdx (需要安装 MDX 集成)
  • .html
  • .js/.ts (as endpoints)

布局

布局是特殊的 Astro 组件,可用于创建可重用的页面模板。布局组件通常放置在项目中的 src/layouts 目录中,但这不是必须的。你可以选择将它们放置在项目中的任何位置。

内容集合

一个内容集合是保留在 src/content 目录中的任何顶级目录,一旦有了一个集合,你就可以使用 Astro 的内置内容 API 开始查询集合,内容集合帮助管理你的文档,校验 frontmatter,并为所有内容提供自动 TypeScript 类型安全。

初始化项目

开发环境准备

生成项目结构

执行命令

Terminal window
1
pnpm create astro@latest

当提示 Where would you like to create your new project?(你想要在哪里创建你的新项目?)时输入文件夹的名称来为你的项目创建一个新目录,例如:./tutorial 然后用方向键选择模板 当提示询问你是否打算编写 TypeScript 时,输入 n (不打算)。 当提示询问 Would you like to install dependencies?(你想现在安装依赖吗?)时输入 y(现在安装)。 当提示询问 Would you like to initialize a new git repository?(你想要初始化一个新的 Git 仓库吗?)时输入 y(要初始化)。

项目生成完成后,可以在开发模式下实时预览

Terminal window
1
pnpm dev

内容

布局

在项目中添加CSS库 Tailwind

Terminal window
1
pnpm.cmd astro add tailwind

astro.config.mjs 中添加配置

1
import { defineConfig } from 'astro/config';
2
import tailwind from "@astrojs/tailwind";
3
4
// https://astro.build/config
5
export default defineConfig({
6
devToolbar: {
7
enabled: false
8
},
9
integrations: [
10
tailwind(),
11
],
12
});

src/components 目录下创建 Header.astro ,编写页头组件;

1
---
2
3
---
4
5
<div class="flex flex-row border-b border-zinc-200">
6
<div class="flex flex-row items-end w-60">
7
<div class="w-10 h-10 bg-[url('/favicon.svg')]"></div>
8
<span class="text-2xl">健兼</span>
9
</div>
10
<div class="grow flex flex-row justify-center">
11
<div class="inline-block text-2xl py-0.5 px-0.5 border-b-2 border-white hover:border-blue-700 hover:text-blue-600">
12
<a href="/">首页</a>
13
</div>
14
<div class="inline-block text-2xl py-0.5 px-0.5 border-b-2 border-white hover:border-blue-700 hover:text-blue-600">
15
<a href="/about/">关于</a>
16
</div>
17
</div>
18
<div class="w-60"></div>
19
</div>

src/components 目录下创建 Footer.astro ,编写页脚组件;

1
---
2
3
---
4
5
<footer class="flex justify-center my-10">
6
<div class="text-xs font-thin">copyright &copy; 2024 JianZhang</div>
7
</footer>

src/layouts 文件加下,创建 BaseLayout.astro 布局文件,确定页面基础布局,通过 Astro.props 读取上层页面传递过来的参数,<slot> 作为当前页面内容的占位符。

1
---
2
import Header from '../components/Header.astro'
3
import Footer from '../components/Footer.astro'
4
import { ViewTransitions } from "astro:transitions";
5
6
const {pgTitle} = Astro.props
7
---
8
9
<html lang="en">
10
<head>
11
<meta charset="utf-8" />
12
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
13
<meta name="viewport" content="width=device-width" />
14
<meta name="generator" content={Astro.generator} />
15
<title>{pgTitle}</title>
16
<ViewTransitions/>
17
</head>
18
<body class="mt-4 px-4 min-h-screen flex flex-col justify-between">
19
<div class="grow flex flex-col justify-start">
20
<Header class="grow-0"/>
21
<slot class="grow"/>
22
</div>
23
<Footer class="grow-0"/>
24
</body>
25
</html>

添加博客文章

设置 TypeScript:

1
{
2
// 注意:如果使用 `astro/tsconfigs/strict` 或 `astro/tsconfigs/strictest`,则不需要更改
3
"extends": "astro/tsconfigs/base",
4
"compilerOptions": {
5
"strictNullChecks": true
6
}
7
}

创建 src/content/posts 文件夹, 并创建 config.ts 定义集合:

1
// 1. 从 `astro:content` 导入适当的工具。
2
import { z, defineCollection } from 'astro:content';
3
4
// 2. 定义要用 schema 验证的每个集合。
5
const blogCollection = defineCollection({
6
type: 'content', // v2.5.0 及之后
7
schema: z.object({
8
title: z.string(),
9
tags: z.array(z.string()),
10
image: z.string().optional(),
11
}),
12
});
13
14
// 3. 导出一个 `collections` 对象来注册你的集合。
15
export const collections = {
16
'blog': blogCollection,
17
};

Astro 将内容集合的重要元数据存储在项目中的 .astro/ 目录, 只要运行astro dev, astro build命令, .astro 目录就会自动更新。

添加 Astro MDX 集成可以使用 JSX 变量、表达式和组件来增强你的 Markdown 编写体验。

Terminal window
1
pnpm.cmd astro add mdx

astro.config.mjs 中添加配置

1
import { defineConfig } from 'astro/config';
2
import tailwind from "@astrojs/tailwind";
3
import mdx from "@astrojs/mdx";
4
5
// https://astro.build/config
6
export default defineConfig({
7
devToolbar: {
8
enabled: false
9
},
10
integrations: [
11
tailwind(),
12
mdx()
13
],
14
});

src/content/posts/ 文件夹下创建博客文章 xxx.mdx, 并添加合适的 frontmatter .

1
---
2
title: '我的第一篇博客文章'
3
pubDate: 2022-07-01
4
description: '这是我 Astro 博客的第一篇文章。'
5
author: 'Astro 学习者'
6
image:
7
url: 'https://docs.astro.build/assets/rose.webp'
8
alt: 'The Astro logo on a dark background with a pink glow.'
9
tags: ["astro", "blogging", "learning in public"]
10
---
11
12
# 我的第一篇博客文章
13
14
发表于:2022-07-01
15
16
欢迎来到我学习关于 Astro 的新博客!在这里,我将分享我建立新网站的学习历程。
17
18
## 我做了什么
19
20
1. **安装 Astro**:首先,我创建了一个新的 Astro 项目并设置好了我的在线账号。
21
22
2. **制作页面**:然后我学习了如何通过创建新的 `.astro` 文件并将它们保存在 `src/pages/` 文件夹里来制作页面。
23
24
3. **发表博客文章**:这是我的第一篇博客文章!我现在有用 Astro 编写的页面和用 Markdown 写的文章了!
25
26
## 下一步计划
27
28
我将完成 Astro 教程,然后继续编写更多内容。关注我以获取更多信息。

src/pages/posts/ 文件夹下,添加 [...slug].astro 文件,定义 getStaticPaths 返回数组, Astro 会将数组元素传递给当前文件生成页面,元素中的 slug 用于生成页面名称,而 props 用于生成页面内容。 getCollection 会返回 src/content/posts 内容集合,通过data访问内容条目的元数据,Content 是转换后的HTML内容, headings 是目录数组。

1
---
2
import { getCollection } from 'astro:content';
3
import BlogPost from '../../components/BlogPost.astro';
4
5
export async function getStaticPaths() {
6
const blogEntries = await getCollection('posts');
7
return blogEntries.map(entry => ({
8
params: { slug: entry.slug }, props: { entry },
9
}));
10
}
11
12
const { entry } = Astro.props;
13
const { Content,headings } = await entry.render();
14
---
15
16
<BlogPost url={`/posts/${entry.slug}`} frontmatter={entry.data} Content={Content} headings = {headings} />

通过 BlogPost 组件, 展示markdown 渲染后的内容

1
---
2
import BaseLayout from '../layouts/BaseLayout.astro'
3
import FormattedDate from './FormattedDate.astro'
4
import BlogTag from "./BlogTag.astro";
5
const { url,frontmatter,Content,headings } = Astro.props;
6
7
---
8
9
<style>
10
:global(.prose>p){
11
text-indent: 2rem;
12
}
13
:global(.prose>h1,.prose>h2,.prose>h3,.prose>h4,.prose>h5,.prose>h6){
14
border-bottom-width: 2px;
15
}
16
:global(.prose>h2){
17
text-indent: 1rem;
18
}
19
:global(.prose>h3){
20
text-indent: 2rem;
21
}
22
:global(.prose>h4){
23
text-indent: 3rem;
24
}
25
:global(.prose>h5){
26
text-indent: 4rem;
27
}
28
:global(.prose>h6){
29
text-indent: 5rem;
30
}
31
32
:global(.prose>h1:after,.prose>h2:after,.prose>h3:after,.prose>h4:after,.prose>h5:after,.prose>h6:after){
33
font-weight: 100;
34
font-size: 1rem;
35
color: rgb(148 163 184);
36
border-width: 2px;
37
margin-left: 4px;
38
}
39
:global(.prose>h1:after){
40
content: 'h1';
41
}
42
:global(.prose>h2:after){
43
content: 'h2';
44
}
45
:global(.prose>h3:after){
46
content: 'h3';
47
}
48
:global(.prose>h4:after){
49
content: 'h4';
50
}
51
:global(.prose>h5:after){
52
content: 'h5';
53
}
54
:global(.prose>h6:after){
55
content: 'h6';
56
}
57
58
:global(.prose nav.toc .toc-level,.prose nav.toc .toc-item){
59
margin-top: 0.25rem; /* 4px */
60
margin-bottom: 2px;
61
}
62
:global(.prose nav.toc){
63
border-style: dashed;
64
border-width: 2px;
65
font-size: 0.75rem; /* 12px */
66
line-height: 1rem; /* 16px */
67
}
68
:global(.prose nav.toc a.toc-link){
69
color: rgb(100 116 139);
70
}
71
:global(.prose nav.toc a.toc-link:hover){
72
text-decoration-color: #1d4ed8;
73
color: rgb(29 78 216);
74
}
75
:global(.prose :not(pre)>code){
76
background-color: rgb(226 232 240);
77
margin-left: 0.25rem;
78
margin-right: 0.25rem;
79
padding-left: 0.25rem;
80
padding-right: 0.25rem;
81
}
82
:global(.prose :not(pre)>code::before,.prose :not(pre)>code::after){
83
content: '';
84
}
85
86
</style>
87
88
<BaseLayout pgTitle={frontmatter.title}>
89
<div class="border rounded mb-4">
90
<div class="bg-slate-50 text-wrap text-center text-4xl py-3">{frontmatter.title}</div>
91
<div class="flex justify-between border border-dashed text-xs font-thin italic">
92
<div><FormattedDate date={frontmatter.pubDate}/></div>
93
<div>{frontmatter.author}</div>
94
</div>
95
</div>
96
<div class="mt-4 pt-2 px-4 flex justify-between">
97
<div class="prose max-w-none w-3/4 px-4 grow">
98
<Content/>
99
</div>
100
<div class="grow-0 w-1/4 px-4 border-solid border-black border-l-2">
101
{
102
frontmatter.tags.map((tag)=><BlogTag tag={tag}/>)
103
}
104
</div>
105
</div>
106
</BaseLayout>

通过 /posts/xxx/ 访问文章

Markdown 经过 Astro 转换的HTML是没有样式的, 通过添加 rehype 集成来美化。

Terminal window
1
pnpm.cmd astro add tailwindcss @tailwindcss/typography rehype-slug rehype-autolink-headings @jsdevtools/rehype-toc

要想 typography 插件生效,还需要在 <Content> 的父元素上添加 prose 类。 rehype-toc 插件负责生成 目录,rehype-slug 和 rehype-autolink-headings 为目录生成 锚链接,在 astro.config.mjs 中添加配置

1
import { defineConfig } from 'astro/config';
2
import tailwind from "@astrojs/tailwind";
3
import rehypeToc from '@jsdevtools/rehype-toc'
4
import rehypeSlug from 'rehype-slug'
5
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
6
7
import mdx from "@astrojs/mdx";
8
9
// https://astro.build/config
10
export default defineConfig({
11
devToolbar: {
12
enabled: false
13
},
14
integrations: [tailwind(), mdx()],
15
markdown: {
16
rehypePlugins: [
17
rehypeSlug,
18
[rehypeAutolinkHeadings, { behavior: 'prepend' }],
19
],
20
},
21
});

自己还可以通过 :global() 给子组件中的内容添加自定义的Markdown 样式。

添加首页

添加 src/pages/index.astro 定义首页,通过 getCollection API 访问内容集合元数据, 展示文章列表和标签列表。

1
---
2
import { getCollection } from "astro:content";
3
import BaseLayout from '../layouts/BaseLayout.astro';
4
import BlogPostItem from "../components/BlogPostItem.astro";
5
import BlogTag from "../components/BlogTag.astro";
6
const allPosts = await getCollection("posts");
7
const pgTitle ='首页';
8
const uniqueTags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
9
10
---
11
<BaseLayout pgTitle={pgTitle}>
12
<div class="w-full h-full flex my-10 px-6">
13
<div class="w-9/12 mx-4 px-4">
14
{allPosts.map((post) =>
15
<BlogPostItem post = {post} />
16
)}
17
</div>
18
<div class="w-1/4 mx-4 px-4 border-solid border-black border-l-2 flex flex-wrap">
19
{uniqueTags.map((tag) =>
20
<BlogTag tag={tag}/>
21
)}
22
</div>
23
</div>
24
</BaseLayout>
目录