这是我的第一个前端作品。我借助 vibe coding,把喜欢的留白、墨线与醒目标题写进网页。本文从界面参观开始,带你获取源码、替换个人资料、写第一篇文章并部署上线。后来,随着发布内容增多,我全面重写了个人网站,也想邀请你看看它现在的样子。
代码开源
如果你也想有个自己的博客,喜欢宽阔的留白、醒目的大字和利落的深色线条,先来看看这件作品。
这个网站把首页、文章、归档和个人介绍放在同一套视觉里。页面留白比较多,按钮有明确的边框,标题也不藏着掖着。拿来放学习笔记、项目记录,或者偶尔写点生活,都能用上。
下面先带你逛一遍,再从获取源码开始,把它变成自己的博客。跟着做完,你会得到一个可以在本地编辑、发布文章,并交给静态托管平台上线的网站。
先看效果:这个博客长什么样
首页,把想说的话放大一点

首屏是「思考、记录、分享」三个大字组,下面接一小段介绍、几个主题标签,以及「浏览文章」「关于我」两个入口。再往下就是最新文章。
这里靠字重、字号、线条和留白分层:最想让人看到的内容放大,次要信息收进灰色文字,边框把不同区域分开。
你可以保留这个布局,把三个词换成自己的表达。比如「学习、动手、记录」,或者干脆放上自己的名字。
文章列表,先让人找到想读的内容

文章按列表展开,每篇带分类、标题、摘要和日期。长标题能换行,不必先给每篇做一张封面才能发布。
点开「筛选」,可以按分类浏览,也可以搜索。搜索范围包含正文,所以哪怕只记得文章里的一条命令,也能拿它试着找回那篇笔记。筛选条件会写进地址栏,刷新后还能保留。
阅读页,长文也有路标

正文在左,目录在右。文章里的标题会成为目录项,读者可以直接跳到想看的章节。阅读页也会展示发布日期、估算阅读时间和相关文章。
写文章时,正常使用 Markdown 标题、列表和代码块即可,不用为每篇重新拼一个页面。标题层级保持清楚,目录才会好用。
归档,把文章排成时间线

归档页可以按年份、月份展开文章,也有分类统计视图。写得久了,它就像一本可以翻回去的目录。
归档依赖文章里的日期,所以后面创建第一篇文章时,我们会把发布日期明确写进文件,避免文章被归到错误的月份。
关于页,让读者认识你

这里放头像、个人介绍、技术栈,再往下可以放自己的项目、经历和联系方式。纸白与墨线的排版保留下来,照片则保留原来的颜色。
别把模板里的经历照搬成自己的介绍。暂时没有项目就少放几个区块,写清楚你正在做什么,也足够让人认识你。
代码来得更快以后,我想把时间留给人的决定
这是我的第一个前端作品,也是一次 vibe coding 实践。页面和代码很快有了雏形。人不必把全部精力花在一行行编辑代码上,省下来的时间,可以拿来想清楚这件作品要为谁解决什么问题。
读者来这里最想找到什么?文章应该怎样归类?首页先讲作者,还是先把内容摆出来?这些问题关乎业务逻辑,也决定页面的样子。醒目的大字、深色线条和大片留白,是我喜欢的表达;内容怎么安排、入口放在哪里,则要贴合真实的阅读需要。
vibe coding 让代码更快地长出来。业务逻辑和审美仍然需要人来判断、取舍。对我来说,它带来的自由,是把注意力从「下一行怎么敲」挪到「我到底想让这页呈现什么」。

我很喜欢这个版本的气质。后来,随着发布的文章和内容逐渐增多,我全面重写了个人网站。如果你想看看它现在的样子,可以直接访问鸿尘客:zhcmqtt.top。

看到这里,如果你也喜欢这份留白与墨线的气质,我们就从这件作品搭起,亲手做一个属于自己的博客。
准备工具,让网站先在电脑上亮起来
这份项目使用 React、TypeScript、Vite 和 Tailwind CSS。文章来自本地 Markdown 文件。本教程从现成的开源项目搭建,不需要先准备数据库、服务器后端或博客管理后台。
你需要一台电脑、一个浏览器、Git、代码编辑器,以及 Node.js 22,版本不低于 22.13。在 Node.js 官网选择 22.x 的安装包,按自己的系统完成安装,npm 会一起装好。Git 也从官网下载对应系统的安装包。编辑器用 VS Code、Cursor,或者你熟悉的其他工具都可以。
打开终端,检查工具是否已经能用:
node -v
npm -v
git --version
如果提示找不到命令,先完成对应工具的安装,再重新打开终端。这里先把环境准备好,后面的报错会少一些。
获取源码,进入正确的目录
在准备存放项目的目录中执行:
git clone https://github.com/zhanghongchen1213/Web_blog.git my-blog
cd my-blog
git checkout 267a119244ad0bbda71745b4771b182e4587d52d
最后一行把源码固定在本文对应的版本,方便对照文件和步骤。Git 可能提示进入 detached HEAD;如果准备长期修改,可以马上建立自己的分支:
git switch -c my-blog
接下来的 npm 命令都在 my-blog 根目录执行。 看到 package.json、package-lock.json 和 vite.config.ts 才是对的,不要进入 client 后再安装。
仓库 README 还保留着旧版全栈方案的介绍,本文对应的是当前 Markdown 静态版本,按下面的步骤走即可。
安装依赖,启动开发服务器
npm ci
npm run dev
npm ci 按锁文件安装依赖,仓库里的 .npmrc 也需要保留。先不要把包管理器换来换去,或者顺手升级全部依赖。
启动后,终端会显示一个 Local 地址。打开它,通常就能看到前面的黑白首页。端口被占用时地址可能变化,以终端实际输出为准。
这个终端需要一直开着。按下 Ctrl+C,开发服务器就停止了。
试着点开「文章」「归档」「关于」,再读一篇文章。确认模板能正常运行后,我们再开始换内容。先看到页面,再改页面,出了问题也容易知道是哪一步引起的。
换上自己的名字、头像和介绍
先认几个会经常打开的文件:
my-blog/
├── package.json
├── vite.config.ts
├── scripts/
│ └── generate-seo-assets.mjs
└── client/
├── index.html
└── src/
├── pages/
│ ├── Home.tsx
│ └── About.tsx
├── components/
│ ├── Navbar.tsx
│ └── Footer.tsx
├── lib/
│ ├── seo.ts
│ └── staticContent.ts
└── content/
└── blog/
不用一口气把整个项目看完。先处理下面这些位置,网站就会开始有你的样子。
| 你想修改什么 | 打开哪个文件 | 具体改哪里 |
|---|---|---|
| 首页大标题、简介、主题标签 | client/src/pages/Home.tsx |
「思考、记录、分享」、介绍段落和几个标签 |
| 导航左侧的站点标识 | client/src/components/Navbar.tsx |
显示 BLOG 的位置 |
| 头像、姓名、技术栈、项目与经历 | client/src/pages/About.tsx |
个人介绍、图片的 src,以及下方各个区块 |
| 页脚介绍与联系方式 | client/src/components/Footer.tsx |
介绍文字、账号链接,以及联系方式按钮中的内容 |
| 网站名称、描述与作者 | client/src/lib/seo.ts |
SITE_NAME、SITE_DESCRIPTION、AUTHOR_NAME |
| 文章数据里的作者名 | client/src/lib/staticContent.ts |
author 对象中默认的 name: "站长" |
| 初始网页标题与描述 | client/index.html |
<title> 和 description 的内容 |
有些页面还单独写了 SEO 描述和个人资料。比如 Home.tsx 的 SEOHead、About.tsx 的 ProfilePage 数据,也要一起换成自己的内容。改完用编辑器全局搜索原站名称、账号和联系方式,能找到遗漏的地方。

把头像存进自己的项目
在 client 下新建 public/images,把自己的照片放进去,比如 avatar.jpg:
client/public/images/avatar.jpg
然后在 About.tsx 找到头像的 <img>,修改它的属性,其他样式先保留:
src = '/images/avatar.jpg'
alt = '我的头像'
路径从 /images 开始,浏览器访问时不需要带上 client/public。后面文章插图也按同样的方式放。
头像出现后,再换介绍、技术栈和项目。联系方式没有对应账号就删掉入口;项目链接不要留下 #;页脚的备案信息使用自己的真实信息,没有可展示的备案信息就删除原来的备案区块。
client/index.html 末尾还带着原站的统计脚本。搜索 data-cf-beacon,删除包含它的整行 <script>。以后需要访问统计时,再配置自己的账号。
现在刷新首页和关于页,检查名字、头像、链接是不是都属于自己。模板负责搭架子,介绍里的主角得换人。
写下第一篇文章
这个博客的文章放在 client/src/content/blog。文件保存后,开发服务器会重新加载内容;首页、文章列表和归档会从这些文件中读取数据。
先把目录里的示例文章备份到工程外,再从这个目录移走。只要 .md 文件还留在 client/src/content 下面,换个子文件夹也会继续被扫描,不适合当备份。
新建一份 Markdown
在 client/src/content/blog 新建 my-first-post.md,填入下面的内容:
---
title: 你好,这是我的第一篇博客
slug: my-first-post
summary: 记录我搭建个人博客的第一步。
category: 生活记录
categorySlug: life
type: blog
status: published
publishedAt: 2026-01-20
---
## 为什么想写博客
我想给学习笔记和项目记录找一个固定的地方。先写下一篇,再慢慢把这里填满。
## 接下来想记录什么
- 学习中遇到的问题和解决办法。
- 项目从想法到完成的过程。
- 值得回头看的生活片段。
顶部两条 --- 之间是文章信息,后面才是正文。示例日期请换成你这篇文章的实际发布日期。
几个字段可以这样理解:
| 字段 | 用来做什么 | 怎么填写 |
|---|---|---|
title |
列表与阅读页的标题 | 写你的文章标题 |
slug |
文章地址的最后一段 | 使用不重复的英文短名,发布后尽量不改 |
summary |
列表摘要 | 用一两句话说清文章内容,保持单行 |
category |
展示给读者的分类名 | 比如生活记录、学习笔记 |
categorySlug |
分类的标识 | 同一分类始终使用同一个英文标识 |
type |
内容类型 | 普通文章填写 blog |
status |
是否展示 | 要出现在网站上,填写 published |
publishedAt |
发布日期与归档位置 | 填写真实日期,格式为 YYYY-MM-DD |
这里用的是简单的单行元信息解析。先按示例填写,别把 summary 写成多行 YAML,也别给多个文件重复使用同一个 slug。

保存文件,回到文章列表。示例文章替换后,页面会变成下面这样:

这里的 published 表示文章在构建的网站中可见。此时它仍运行在你的电脑上,真正上线还要完成后面的构建和上传。
给文章放一张自己的图片
准备一张真实的照片或截图,命名为 my-project.png,放到:
client/public/images/my-project.png
在文章末尾追加:
## 留下第一张图片

文件名要和真实文件一致,图片格式也保持一致。不要把一张 PNG 只改后缀,当成 JPG 使用。
打开 /article/my-first-post,就能看到标题、正文、图片和右侧目录。下面是本地示例的实际页面,插图使用前面的博客首页截图:

再做三个小动作:到文章列表搜索「生活片段」,去归档找这篇文章,最后在阅读页直接刷新。文章都能找到、刷新后还能读,第一篇就接进来了。
顺便给目录跳转留一点空间。顶部导航固定在页面上,在 client/src/index.css 末尾追加下面的样式,跳转后的标题就能留在导航下方:
.prose-brutalist :is(h1, h2, h3) {
scroll-margin-top: 100px;
}
保存后点一次目录,确认对应标题能完整露出来。
把发布目录准备好
本地页面跑起来后,我们要把它打包成托管平台能接收的文件。这个版本有两处配置需要一起调整,避免上线时漏掉站点地图,或仍然指向原作者的域名。
让 SEO 文件和图片进入同一个目录
打开 scripts/generate-seo-assets.mjs,找到原来的这一行:
const OUTPUT_DIR = resolve(ROOT_DIR, 'public')
改成:
const OUTPUT_DIR = resolve(ROOT_DIR, 'client/public')
Vite 在这个项目里读取的是 client/public。这样生成的 sitemap.xml、robots.txt 和你放进去的图片,才会一起复制到发布目录。
让域名配置被两边读到
在项目根目录新建 .env.production,先填入下面这一行:
VITE_SITE_URL=http://localhost:4173
这是本地预览用的临时地址,上线前会换成托管平台给你的实际地址。文件名包含开头的点,确认没有被编辑器保存成 .env.production.txt。
Vite 会读取这个文件,但前面的 Node.js 生成脚本不会自动读取。打开 package.json,只替换 scripts 中的 seo:generate 值,其他脚本保留:
"seo:generate": "node --env-file=.env.production scripts/generate-seo-assets.mjs"
现在页面和 SEO 生成脚本可以使用同一份地址。VITE_ 开头的变量会进入前端构建结果,这里只存网站公开地址,不放密码或密钥。
检查、构建,再看一次发布版本
在项目根目录依次执行:
npm run check
npm run build
npm run start
如果前一条失败,先解决报错,再运行下一条。check 检查 TypeScript,build 生成发布文件,start 在本地预览构建结果。
构建产物放在 dist/public,结构大致如下:
dist/public/
├── index.html
├── assets/
├── images/
├── sitemap.xml
└── robots.txt
打开终端显示的预览地址,再看首页、第一篇文章和图片。分别访问 /sitemap.xml、/robots.txt,确认前者显示 XML,后者显示文本,里面的网站地址也是刚刚设置的地址。
start 用于本地预览。下面上线时,我们上传的是构建文件,开发服务器不用搬到云端常驻。
把博客上线到 Cloudflare Pages
这条路线使用 Cloudflare Pages 的 Direct Upload,直接上传构建好的文件。你需要自己的 Cloudflare 账号,暂时没有个人域名也可以先用平台提供的默认地址。

先完成第一次上传
- 登录 Cloudflare 控制台,进入 Workers & Pages。
- 选择创建应用,进入 Pages 的文件拖放上传入口。控制台通常会显示
Drag and drop your files。 - 输入项目名称,上传本地
dist/public文件夹。上传内容的根层应直接包含index.html和assets,不要把整个源码工程或多包了一层的dist上传。 - 点击部署,等待平台完成处理,记录控制台实际提供的默认访问地址。
打开这个地址,首页应该能显示。再直接访问 /article/my-first-post,按刷新键,确认文章也能正常打开。
这个项目使用浏览器路由。Pages 在没有顶层 404.html 时会按单页应用方式回退到首页入口,再由 React 匹配文章地址,因此不要额外塞入一个顶层 404.html 来「补页面」。项目里已有站内 404 路由。
把临时地址换成真正的站点地址
第一次上传是为了拿到实际可访问的默认地址。接下来打开 .env.production,把本地预览地址换成控制台给你的地址。下面只是填写格式,不能照抄项目名:
VITE_SITE_URL=https://your-blog.pages.dev
重新构建:
npm run check
npm run build
在这个 Pages 项目中创建一次新部署,选择 Production,再上传新生成的 dist/public。域名信息也就跟着换过去了。
更新完成后,检查线上首页、文章直达与刷新、图片、搜索、归档,以及 /sitemap.xml、/robots.txt。站点地图里的文章地址应属于你的网站,不应再出现本地地址或原站域名。
有站点地图不代表已经被搜索引擎收录。这个版本的正文由浏览器执行 JavaScript 后显示;如果以后更在意内容抓取和收录,可以再做预渲染。先把可访问、可阅读的博客搭好。
有自己的域名时,再接上去
默认地址能正常访问后,再进入项目的 Custom domains,选择添加自定义域名。
使用子域名时,比如自己的 blog 子域,先在 Pages 中关联它,再到域名的 DNS 管理处设置 CNAME,目标填写这个项目实际的默认域名。域名仍在其他 DNS 服务商那里,也可以走这条子域路线。
如果直接使用根域名,则需要按 Cloudflare 的要求把域名加入对应账号,并配置它的名称服务器。跟着控制台提示完成,不用为了第一次发布同时折腾两套域名方案。
域名生效后,再把 .env.production 改成新的正式地址,重新构建并部署一次。打开站点地图核对域名,确认旧地址没有继续留在里面。
Direct Upload 后续需要你主动上传新构建。同一个项目不能直接切换为 Git 集成模式;如果以后想提交代码后自动部署,可以另建 Git 集成的 Pages 项目。开始阶段先把这一次上传做明白就够了。
第二篇文章怎么更新
以后写文章,重复的是这几个动作:
- 在
client/src/content/blog新建一份.md,填写自己的标题、摘要、分类、唯一slug和真实日期。 - 用
npm run dev在本地检查正文、目录和图片。 - 执行
npm run check和npm run build。 - 在同一个 Pages 项目里上传新的
dist/public,创建生产部署。

已经上线的网站不会因为你保存了本地文件就自动更新。忘了重新构建或上传,浏览器看到的仍然是上一次的内容。
留草稿时,可以把 status 改成 draft,它不会出现在正常的文章列表中。不过 Markdown 会进入前端构建,不适合存放私人记录或密钥。真正不打算公开的内容,留在工程外。
遇到问题时,先按现象查对应的位置:
| 现象 | 先检查哪里 |
|---|---|
npm ci 报 Node 版本不支持 |
node -v 是否满足前面的版本要求,安装目录和 .npmrc 是否正确 |
| 安装时出现依赖漏洞提示 | 安装报错与审计提示分开看,按 npm audit 的结果处理依赖;不要直接强制升级全部包来跳过问题 |
| 文件写好了,文章列表里没有 | 文件是否在内容目录、type 是否为 blog、status 是否为 published,以及元信息边界是否完整 |
| 图片在本地显示,上线后不见了 | 文件是否进入 dist/public/images,文件名大小写和 Markdown 路径是否一致 |
| 首页能打开,文章刷新却失败 | 上传层级是否正确,是否添加了顶层 404.html,托管平台是否按单页应用回退 |
| 文章日期或归档月份不对 | publishedAt 是否填写了有效的真实日期 |
| 页面是新内容,站点地图还是旧域名 | .env.production、seo:generate 的读取配置,以及是否重新构建并上传 |
回到开头那张首页图:大标题、几条标签,一小段介绍。它可以装下不少东西,但不用在第一天就填满。
先把名字换成自己的,再写完第一篇。黑白框架已经在那里,接下来让它长出你的内容。
参考资料
鸿尘客 Honchic · 把脑洞焊到电路板上