Note: This page does not support English, using the default language version
一直想抽空搭一个完全属于自己的个人博客。
前阵子在逛 Astro 官网的主题市场时,一眼就相中了 Momo 这款主题。
它最吸引我的地方就是那股纯白、利落的极简风
没有任何花里胡哨的组件,能让人把心思完全放在文字本身。
此外,Astro 标志性的“孤岛架构”让生成的静态页面几乎没有冗余代码,加载速度快得惊人。
我把这次建站的完整流程记录下来,既作为日后重新配置的参考,也希望能帮到想搭建同款博客的朋友。
Node.js® 是一个免费、开源、跨平台的 JavaScript 运行时环境。
Astro 基于 Node.js 开发,因此你需要先安装 Node.js 运行环境。
如果你尚未安装 Node.js:
请访问 https://nodejs.org/ 下载并安装 Node.js 运行环境。
网上有许多详细的 Node.js 安装教程,你可以搜索“Node.js 安装教程”找到适合的指南。
安装完成后,打开你的终端,输入以下命令验证安装是否正确:
第一个命令:
node -v第二个命令:
npm -v如果看到类似这样的版本号输出,说明安装成功:
v25.6.0 # Node.js 版本11.9.0 # npm 版本如果这些命令提示“未找到命令”或显示错误信息,
请重新检查 Node.js 的安装步骤,确保已将 Node.js 添加到系统环境变量中。
相比npm,为何推荐使用pnpm?它在以下两个方面优势显著:
极致的安装速度 pnpm采用独特的硬链接机制管理依赖,避免了npm复制文件的操作,
通常能将依赖安装时间缩短数倍。
高效的磁盘利用 在pnpm的全局存储模式下,同一个版本的依赖包在磁盘上只会保存一次。
你创建的所有项目都可以共享这些依赖,从而节省大量磁盘空间。
安装 pnpm 非常简单,只需在终端中执行以下命令即可:
npm install -g pnpm@latest-10安装完毕后,可通过以下命令来验证是否安装成功:
pnpm -v如果终端显示出 pnpm 的版本号(例如 10.29.3),就说明 pnpm 已准备就绪。
获取 Momo 主题最推荐的方式是通过 Git,这样方便后续直接拉取源码并同步主题的日常更新。
当然,你也可以直接在 GitHub 仓库页面点击 “Download ZIP” 下载压缩包。
如果选择使用 Git,前往 Git 官网下载安装完成后,可在终端执行以下命令进行验证:
git --version如果终端正确输出了类似 git version 2.xx.x 的字样,说明 Git 已成功安装。
推荐使用 Visual Studio Code 作为编辑器,用来修改配置和撰写文章会更加顺手。
安装完成后,进入博客项目文件夹,右键 →「通过 Code 打开」 即可开始编辑。
Momo 是一个「开箱即用」的 Astro 博客主题,
不需要复杂的配置和主题合并,克隆下来就能跑。
你可以创建一个 Blog 的项目文件夹然后执行:
git clone https://github.com/Motues/Momo.git克隆完成后双击进入 Momo 文件夹执行:
pnpm install这一步会自动安装 Astro、Tailwind、Pagefind 等所有依赖。
在终端中执行以下命令,启动 Astro 的本地开发服务器:
pnpm dev启动成功后,终端会输出一个本地访问地址。
打开浏览器访问:
http://localhost:4321/如果一切顺利,这时候一个博客在本地就算是初始化完成了。
重要的事情说三遍!!!
后续所有步骤均需在 Blog\Momo 项目根目录下执行。
重要的事情说三遍!!!
后续所有步骤均需在 Blog\Momo 项目根目录下执行。
重要的事情说三遍!!!
后续所有步骤均需在 Blog\Momo 项目根目录下执行。
在这个主题原版的基础上,我做了一些个性化修改。
如果你也喜欢同样的风格,可以跟着下面的步骤一起调整,将博客打造得和我完全一致。
为了让博客具备完善的 SEO(搜索引擎优化)和极致的加载速度,需要先补充两个底层插件:
@astrojs/sitemap:自动生成网站地图,方便搜索引擎抓取你博客的全部文章。@zokki/astro-minify:自动压缩 HTML、CSS 和 JS 代码,进一步压榨页面体积,让加载速度更快。在终端里先执行 Ctrl + C 停掉刚才的本地服务,然后运行以下命令安装它们:
pnpm add @astrojs/sitemappnpm add @zokki/astro-minify找到并打开 astro.config.mjs 文件。
为了让刚安装的两个插件生效,需要把它们配置到文件里。
在开头添加 import 导入插件
import sitemap from "@astrojs/sitemap";import { minify } from '@zokki/astro-minify';修改 site 为你的域名,
并在 integrations 里找到 svelte() 在后面添加 sitemap(), minify() 调用函数
export default defineConfig({ site: siteConfig.rootSiteUrl || 'https://example.com', // Root URL of site i18n: { locales: i18nConfig.supportedLanguages, defaultLocale: i18nConfig.defaultLanguage, routing: { prefixDefaultLocale: false, redirectToDefaultLocale: false } }, integrations: [icon({ include: { "fa6-brands": ["*"], "fa6-solid": ["*"], "simple-icons": ["*"], "vscode-icons": ["*"], "material-symbols": ["*"], "fluent": ["*"], } }), svelte(),sitemap(), minify(),修改 title rootSiteUrl 然后把 comments 的 enable 改为 false
export const siteConfig: SiteConfig = { title: "example", // Title of the site, used in the tab in the browser and in SEO subTitle: "Blog", // Subtitle of the site rootSiteUrl: "https://example.com", // Root URL of the site, used for generating absolute URLs for SEO and social sharing
favicon: "/favicon/favicon.ico", // Path of the favicon, relative to the /public directory
pageSize: 6, // Number of posts per page toc: { enable: true, depth: 3 // Max depth of the table of contents, between 1 and 4 }, blogNavi: { enable: true // Whether to enable blog navigation in the blog footer }, comments: { enable: false, // Whether to enable comments platform: "default", // Comment platform, set "default" to use Momo-backend, also supports "twikoo" backendUrl: "https://api-momo.motues.top" // Backend URL for comments }, theme: { AOS: true, // Whether to enable AOS (Animate On Scroll) for animations LQIP: true, // Whether to enable LQIP (Low-Quality Image Placeholder) for image placeholders PhotoSwipe: true, // Whether to enable PhotoSwipe for image viewer imageCollage: { enable: true, // Whether to automatically arrange consecutive images into a grid (collage) maxColumns: 4 // Max images per row in a collage (2 - 6); the actual number is chosen automatically }, postCard: { imageMode: "top" // Cover image mode for article cards: "top" shows the image above the content; "background" uses the image as the card background, fading to transparent from right to left }, photoCover: { enable: false, // Whether to use a full-screen photo as the background of the home page; the title and subtitle are centered, and everything smoothly returns to the normal style as you scroll down image: "/cover.jpg", // Photo path: relative to the /public directory if it starts with '/', otherwise relative to the /src directory (e.g. assets/cover.jpg). The blurred placeholder is generated at build time, so there is no small image to prepare by hand mask: 0.5 // Opacity (0 - 1) of the black mask over the photo, fading away as you scroll down }, overlayScrollbars: { enable: true, // Whether to replace the browser's default scrollbar with OverlayScrollbars (overlay style, can auto-hide) autoHide: "leave", // When to hide the scrollbar: "never" always visible; "scroll" hidden unless scrolling; "move" hidden unless the pointer moves over the page or the user scrolls; "leave" hidden when the pointer leaves the page or the user isn't scrolling size: 8 // Scrollbar thickness in pixels } }, expressiveCode: { enable: true, // Whether to enable Expressive Code for code blocks; when false, code blocks fall back to plain text without highlighting (same in the CMS preview) theme: "one-dark-pro" // Shiki theme of code blocks, e.g. "one-dark-pro", "github-dark", "vitesse-dark"; one theme is used for both light and dark mode }}这里只改 indexPage 和 startYear 就行
export const profileConfig: ProfileConfig = { avatar: "assets/avatar.png", // Relative to the /src directory. Relative to the /public directory if it starts with '/' name: "example", // Used in the footer of the blog description: "example", // Used in SEO indexPage: "https://github.com/example", // The homepage, used in footer and SEO startYear: 2025, // The year the site was created, used in the footer}找到 I18nConfig 修改 title subTitle
export const i18nConfig: I18nConfig = { defaultLanguage: "zh-cn", // Default language of the site supportedLanguages: ["zh-cn", "en"], // List of supported languages translations: { // Translation content for each supported language "zh-cn": { Cover: { title: { home: "欢迎来到我的博客!", archive: "文章归档", about: "关于", friends: "友链", }, subTitle: { home: "example", archive: "共 {count} 篇文章", // {count} will be replaced with the total number of articles about: "", friends: "", } } }, "en": { Cover: { title: { home: "Welcome to My Blog!", archive: "Archive", about: "About", friends: "Friends", }, subTitle: { home: "example", archive: "Total of {count} articles", about: "", friends: "", } } } }};隐藏 Dark Mode
<!-- Dark Mode <ThemeIcon class="flex items-center justify-center w-10 h-10 hover:bg-[var(--button-hover-color)] rounded-lg hover:text-current active:scale-90 border border-[var(--button-border-color)] transition duration-300"/> -->找到 updateNavBar 里的 const scrolled = y > 0; 修改为 const scrolled = false; 隐藏顶部导航栏跟随
function updateNavBar() { ticking = false; const y = window.scrollY; if (y === lastScrollY) return; lastScrollY = y; if (!navBar || !navBarList) return; const scrolled = false; navBar.classList.toggle('fixed', scrolled); // 外壳(描边 / 底色 / 阴影 / 模糊)全部由 --nav-chrome 派生,这里只负责翻状态: // 0 = 顶部,只有文字与当前页色块(没有边框、没有底色) // 1 = 吸顶,完整毛玻璃胶囊 // photo-cover.css 会把照片封面页的这个变量换成滚动进度 --photo-p, // 于是首页是逐帧插值,而不是滚一像素就硬切 —— 所以状态类仍然要照常维护。 // 打在两层上是同一个状态的两个落点:#nav-bar 供 photo-cover.css 覆盖, // #nav-bar-list 是外壳本身,两行都由这只 updateNavBar() 幂等对账。 navBar.classList.toggle('nav-scrolled', scrolled); navBarList.classList.toggle('nav-scrolled', scrolled); }找到 page404 修改 title subTitle errorCode
page404: { title: "404", subTitle: "page not found or removed", backToHome: "Home", backToPreview: "Previous Page", errorCode: "Error Code: 404", notice: "Perhaps you can try:" },找到 page404 修改 title subTitle errorCode
page404: { title: "404", subTitle: "页面不存在或已被移除", backToHome: "返回首页", backToPreview: "返回上一页", errorCode: "错误代码:404", notice: "或许你可以尝试:" },MainPageLayout 替换下面这一段代码
<MainPageLayout title={pageTitle} lang="zh-CN"> <div class="mx-auto w-full max-w-[var(--page-width)] px-4 text-center"> <div class="my-8"> <h1 class="text-7xl md:text-8xl font-bold text-black dark:text-white mb-2">{t("page404.title")}</h1> <p class="text-xl md:text-2xl text-[var(--text-color-80)]">{t("page404.subTitle")}</p> </div>
<div class="mt-8 flex flex-col items-center"> <p class="text-[var(--text-color-70)]">{t("page404.notice")}</p> <div class="mt-4 flex gap-4"> <a href={indexPage} class="px-6 py-2 border border-[var(--button-border-color)] rounded-lg text-[var(--text-color)] hover:bg-[var(--button-hover-color)] transition-all"> {t("page404.backToHome")} </a> <a href="javascript:history.back()" class="px-6 py-2 border border-[var(--button-border-color)] rounded-lg text-[var(--text-color)] hover:bg-[var(--button-hover-color)] transition-all"> {t("page404.backToPreview")} </a> </div> </div> </div></MainPageLayout>在完成上述的所有个性化修改后,你就搭建好了属于你自己的博客。
接下来,就是学会如何预览最终打包效果,以及如何开始撰写你的第一篇文章。
Momo 主题内置了快捷命令,可以快速创建新文章。
创建命令:
pnpm momo new <文章路径><文章路径>:文章的文件夹名,同时也是 slugId使用示例:
# 创建文章pnpm momo new hello-world执行后会自动生成对应的 Markdown 文件:
src/content/blog/hello-world/└── zh-cn.md生成的模板内容如下:
---title: hello-worldpubDate: 2026-10-18description: ""image: ""draft: falseslugId: hello-worldcategory: ""pinTop: 0---## hello-world
在这里开始写作…根据实际需求修改 title、description、category 等字段,然后在 ## Title 下方撰写正文即可。
文章写完后,在项目根目录下执行:
pnpm build执行后,Astro 会在根目录生成 dist 文件夹,里面全是优化压缩后的纯静态文件。
pnpm preview该命令会启动一个本地静态服务器,完美模拟线上真实环境。
打开浏览器访问:
http://localhost:4321/在这里可以确认新文章是否正常显示,以及整体效果。
确认无误后,就可以进入下一步
发布到互联网。
这里推荐使用 Cloudflare Static Workers,完全免费且自带全球 CDN 加速。
为了省去配置 Git 的麻烦,我们直接选择上传文件夹功能。
pnpm build,此时根目录下会生成一个 dist 文件夹。dist 文件夹,点击上传。注意:上传之前需要先选择 高级设置 -> 未找到处理 -> 404-page 配置好404页面
等待上传完成,Cloudflare 就会生成一个免费的 *.workers.dev 网址,博客正式上线!
注意:以后每次写了新文章或修改了配置,都需要在本地重新执行
pnpm build,
并将新生成的dist文件夹再次选择上传覆盖。
如果你有自己的域名托管在 Cloudflare,可以让博客看起来更专业:
blog.example.com)。绑定成功后,稍等几分钟,你就可以用自己的专属域名访问博客了。
回看整个搭建过程,
从最初的环境配置到最终的线上部署,
核心其实只有三步:配好基础环境、按需微调配置、打包上传托管。
借助 Astro 的极致性能与 Momo 主题的极简设计,我们不仅在本地搭建起了一个纯粹的文字空间,
更通过 Cloudflare Static Workers 让它获得了免费、稳定且全球加速的线上家园。
这种“无数据库、无繁琐后台”的静态方案,完美契合了独立博客的初衷
让技术退居幕后,让文字重回焦点。
至此,博客搭建已正式完工。
在接下来的日子里,剩下的事情就是安安静静地把心思放在内容上。
不妨现在就静下心来,敲下键盘,去记录、去表达、去留住属于你的每一个瞬间。
祝你写作愉快!