1. 在动手前,先想清楚这个“第三个”到底要解决什么问题
标题写的是“我的第三个网站”,听起来就是一次普通的重新建站,但只有亲手做过两轮、又陆续放弃过两个网站的人,才会明白“第三个”这三个字的分量。它不是数量上的累加,而是一次非常明确的转折:前两个网站教会了我“能上线”和“能长期用下去”是两件事。
我这次做的是自己的个人网站,定位很朴素:一个放技术笔记、项目作品和个人介绍的地方。没有在线商城,没有用户系统,不搞复杂后台。如果只追求“炫”,第三个网站根本没必要做;我要的是一个能安安静静跑很多年、更新起来不痛苦、成本极低的站点。如果你也正准备做自己的网站、作品集,或者想把手里的内容从某个碎片平台搬到自己的域名下,这篇文章应该能帮你少踩几个我踩过的坑。
1.1 前两个网站留下的教训
我的第一个网站,是大二还是大三那会儿做的个人主页。说是网站,其实就是一堆手写的 HTML 页面,CSS 全靠复制粘贴,图片东一张西一张堆在文件夹里。当时觉得能打开就是胜利,但没过多久就发现问题了:想改一个导航栏的文字,得打开十几个文件挨个改;想把“作品”从两行变成三行,样式全乱。最后我干脆把整个目录删掉重做。这个阶段最大的教训是:网站的生命力不在页面漂亮不漂亮,而在内容更新和结构调整是不是方便。
第二个网站就“正规”多了。我上了动态博客系统,配了数据库、在线后台,能直接在网页里打字发布文章,还能装插件。刚开始确实爽,后台登录、写文章、传图片,一切都像那么回事。可后来维护成本慢慢变得让人烦躁:动态程序每隔一阵就提示更新,数据库偶尔连接失败,插件之间还会冲突;更烦的是各种扫描程序天天来问候后台登录页,垃圾评论多到需要专门挂过滤插件。我陷入了“不停修东西”而不是“持续写东西”的循环。一个内容网站,把作者的时间和热情全耗在处理系统问题上,这方向从一开始就不对。
1.2 定边界:第三站不是“什么都有”
所以到第三个网站,我做的第一件事不是选技术,而是先列需求,并且大胆地做减法。我把能想到的功能全部写出来,然后自己给自己打分:没有它,我还能好好更新吗?不能去掉的才算核心功能。
| 需求 | 优先级 | 我的最终方案 |
|---|---|---|
| 发布文章 | P0 | 本地写 Markdown,构建后发布 |
| 展示作品 | P0 | 用结构化 Markdown 数据生成项目页 |
| 个人简介 | P1 | 单独一个静态页面 |
| 评论互动 | P2 | 邮件联系方式 + 外部评论区 |
| 在线聊天 | P3 | 不做 |
| 后台管理 | P3 | 不做 |
| 会员系统 | P3 | 不做 |
把 P3 的需求一个个划掉之后,整站其实就剩三类东西:文章、作品、关于我。这个边界非常重要,因为后续所有的技术选型都基于这个边界展开。没有用户系统,就不需要考虑登录态;没有在线评论,就不需要维护实时数据库;没有后台,就少了一大块安全漏洞面。做个人网站,功能少不是缺憾,反而是能长期运行的前提。
注意:这里的“不做评论”“不做后台”不是绝对标准。如果你的网站确实需要用户投稿、实时聊天这类功能,那静态方案就不合适。边界要跟着你的实际用途走,而不是跟着别人的推荐走。
2. 技术选型:为什么这次的组合更像“减法”
很多人做网站,习惯先听别人说最近哪个框架火、哪个工具流行,然后一头扎进去。我在第三个网站上正好反过来:先问自己“这个需求需要多复杂”,再选尽可能简单的方案。最后定下来的是静态站点生成器 + 对象存储 + CDN,整套链路里没有一个常年运行的应用服务器,也没有数据库。你可能觉得这不够“技术”,但对内容型个人网站来说,这恰恰是最稳的组合。
2.1 静态化是个人网站的主流答案
动态网站和静态网站的核心区别,可以理解成一个是“现炒现卖”,一个是“预制菜”。动态方案是用户每次访问,服务器都要去数据库里取数据、渲染模板、生成页面再返回;静态方案是在构建阶段就把所有 HTML 页面生成好,用户访问时直接把现成的文件扔给浏览器。对内容更新频率不高、以阅读为主的个人网站来说,“预制菜”效率高太多了。
我拿前两个网站做对比,印象特别明显:
| 对比维度 | 动态方案 | 静态方案 |
|---|---|---|
| 访问速度 | 依赖服务器性能、数据库查询速度 | 文件直接由 CDN 边缘节点返回 |
| 安全 | 暴露后台、插件、运行环境,攻击面大 | 没有运行时、没有后台,攻击面很小 |
| 维护 | 要更新程序、备份数据库、清垃圾评论 | 内容就是文件,构建发布后即可 |
| 扩展能力 | 适合实时交互、强功能应用 | 适合内容展示、作品集、文档站 |
当然,静态化也不是万能的。如果页面需要根据每个用户显示不同内容,比如“你好,某某”,或者要做即时聊天、表单提交后的实时反馈,那么纯静态方案就非常吃力。这也是我一开始强调边界的原因:技术选型不是找最强的,而是找一个刚好覆盖你需求、长期维护成本最低的。
2.2 内容层:用 Markdown 文件替代数据库
第二个网站让我最难受的,是内容被锁在数据库里。想备份要导出,想迁移要转换,甚至想用脚本批量修改某个错别字都费劲。第三个网站我彻底换了个思路:所有文章和作品信息都用 Markdown 文件保存,数据库直接退场。
每篇文章其实就是一个带“头部信息”的文本文件:
--- title: "我的第三个网站终于上线了" date: 2024-06-12 tags: [个人网站, 静态化, 部署] summary: "从动态博客转向静态化个人网站的过程记录" draft: false --- 这里是文章正文,用纯文本写就行。文件放在项目文件夹里,天然就是文本,可以被版本管理系统追踪。内容改动在不在,谁改过,什么时候改的,全都有记录。最舒服的是写文章不再依赖那个动不动就弹更新的在线后台,我可以在任何顺手的文本编辑器里写,写完存盘提交就完事。文本文件比数据库后台更透明、更好迁移,想换一个静态站点生成器也几乎无成本,把 Markdown 文件和目录结构保留好就行。
2.3 部署层:对象存储加 CDN 的组合
前两个网站要么直接租虚拟主机,要么买云服务器自己搭环境。第三个网站我选择把生成的静态文件传到对象存储,再用 CDN 做分发。简单说,对象存储就是一个可以放文件、可以让大家访问的“网盘”,CDN 则在世界各地都有缓存节点,用户就近拉取文件。
这套方案有几个实打实的好处:
- 不需要维护操作系统,不存在“忘了打补丁”的问题。
- 没有 24 小时必须开着的应用服务器,成本低到可以忽略。
- 文件分发有 CDN 兜底,访问量和带宽压力被边缘节点挡掉。
我自己的实践感受是,维护成本几乎降到了零。以前登录后台第一反应是“有没有更新要处理”,现在打开管理界面只会看见“这个月有几个新访问来源”这类数据。做个人网站最需要的不是掌控感,而是把系统层面的麻烦全部外包,留出时间写内容、打磨页面。
提醒:静态文件放对象存储后,一定要把“公开读”权限配好,但不要把整个存储桶的“写权限”暴露出去。平时发布走自动化工具,不要手动去管理后台上传,减少误操作。
3. 核心实现:从空白目录到一个能上线的站点
技术选型定完,剩下的就是具体把网站“长”出来。我给第三个网站定的目录结构并不复杂,重点在于让内容、模板、脚本、资源各归其位。
3.1 内容目录怎么组织
用文字描述不如直接看目录树:
my-site/ ├── content/ │ ├── posts/ # 文章,每篇一个 Markdown 文件 │ │ ├── hello-world.md │ │ ├── project-lessons.md │ │ └── ... │ └── projects/ # 作品集,每个项目一个 Markdown 文件 │ ├── tool-a.md │ └── tool-b.md ├── assets/ │ ├── images/ # 文章和页面用到的原始图片 │ └── css/ ├── templates/ # 页面模板,用来把 Markdown 渲染成 HTML │ ├── home.html │ ├── post.html │ └── project.html ├── scripts/ │ └── build.py # 构建脚本 ├── public/ # 构建结果,最终上传的目录 └── config.toml # 站点全局配置文章放content/posts,作品放content/projects,模板单独放templates,构建结果统一生成到public。这个结构最大的好处是“约定大于配置”:新文章只要按命名规则丢到对应文件夹,重新构建就能自动出现在列表页里。不需要去某个管理后台填写分类、设置别名,一切都跟着文件夹和文件头部信息走。
3.2 页面生成逻辑和数据模型
第三个网站的页面生成逻辑,本质上是一个非常简单的模板引擎练习:读取 Markdown 文件,解析头部信息,把正文转成 HTML,然后套进模板。我在构建脚本里写了一个核心函数,思路大致是这样:
def build_post(src_path, templates): # 1. 读取 Markdown 文件并解析 front matter meta, content = parse_markdown(src_path) # 2. 把正文转成 HTML body_html = markdown_to_html(content) # 3. 选择文章模板并填充数据 html = templates["post.html"].render( title=meta["title"], date=meta["date"], tags=meta["tags"], content=body_html, ) # 4. 按文章 slug 生成目录,写出 index.html out_dir = Path("public") / meta["slug"] out_dir.mkdir(parents=True, exist_ok=True) (out_dir / "index.html").write_text(html, encoding="utf-8")数据模型也很简单:一篇文章拥有标题、日期、标签、摘要、链接别名(slug)、正文这几个字段。列表页和标签页的排序逻辑,无非是把所有文章按日期倒序排,再按标签分组。作品页则多了“项目状态”“技术栈”“链接地址”这几个字段,本质上和文章模型差不多。
这个阶段给我最大的启发是:绝大多数个人网站,并不需要一个庞大的后台框架。一个遍历文件夹、读文件、渲染模板的脚本,几十行代码就能做到。真正花时间的反而是组织好这些数据,让页面风格统一、浏览逻辑顺畅。
3.3 三处不能忽略的细节:图片、缓存、链接
页面能生成,只是万里长征第一步。有三个细节我在第三站里单独做了处理,它们是决定网站“用起来香不香”的关键。
第一是图片。以前我习惯直接把原图丢进去,一张照片动不动两三兆,打开文章像在加载一个相册。这次我在构建流程里加了一步图片处理:上传前先压缩,统一转成适合网页展示的格式,并按需生成不同尺寸。同一张图,移动端加载小图,桌面端加载大图,不再让访客的流量白白浪费。
第二是缓存策略。静态站点的文件几乎不变,特别适合长时间缓存。我在 CDN 层对带文件指纹的资源设置了很长的缓存时间,比如 CSS、JS、压缩图这些文件名带了哈希值的资源,缓存一年都没问题;而 HTML 页面缓存时间设置得短一些,方便内容更新后尽快生效。这样做之后,页面重复访问速度快了很多,回源压力也几乎为零。
第三是链接结构。个人网站最怕文章改个链接别名,旧地址全变成 404。我从一开始就规定好每篇文章的 slug,一锤定音,轻易不改。如果实在要改,就在构建时生成一份旧地址到新地址的重定向关系。顺便用脚本检查站内所有内部链接,确保没有死链。
3.4 评论、统计和搜索的轻量替代
需要交互怎么办?我给第三站选的是“轻量替代”。评论没有在站内直接做,而是在每篇文章底部放了“发送邮件讨论”的入口,同时接了一个外部评论区,用户点击就能跳出去发言。这样既保留了互动可能,又不给自己增加内容审核和数据库维护的负担。
访问统计同样不依赖重脚本。我定期把 CDN 的访问日志拉回来,在本地简单分析一下来源、页面浏览量和热门内容。数据足够看出“哪篇文章值得继续写”,又不会为了加载一个统计脚本拖慢页面。搜索则更简单,构建时把所有文章标题、摘要、正文内容生成一份 JSON 索引,页面上用一个小的搜索框做前端检索,实时返回结果。没有后端接口,也不需要付费搜索服务,这对个人站已经绰绰有余。
4. 从本机到线上:我把发布做成了“一键”
网站做到这一步,内容模型和页面模板都清晰了,最后真正让第三个网站“活”起来的是发布流程。我不想再像第二个网站那样,每次更新都打开后台,一边写一边担心保存失败。我想要的工作流是:本地写、保存、提交,然后网站自动更新。
4.1 本地工作流的几个常用命令
我特意把所有频繁用到的操作封装成了简单命令,给自己减少记忆负担:
make serve # 本地预览,改文件后自动刷新浏览器 make check # 检查链接是否有效、文章头部信息是否完整 make build # 生成 public/ 目录 make deploy # 同步到对象存储并刷新 CDN 缓存本地预览解决“写的时候能看见效果”,检查命令解决“别把带病内容发出去”,构建命令负责把 Markdown 变成最终文件,发布命令负责把最终文件推上云。平时更新一篇文章,我只需要写 Markdown,然后依次按下make build和make deploy,全程不过一分钟。如果哪天改了模板或新增了页面结构,也只需要在本地多预览几次确认样式没问题,再构建发布。
4.2 自动发布流水线:提交后就更新
后来我连手动执行都嫌麻烦,又加了一步自动化。核心思路是:主分支收到新提交后,自动完成从拉代码到刷新缓存的整个过程。流程写成伪代码大概是:
触发条件:代码托管平台收到主分支推送 1. 拉取最新源码 2. 安装构建工具依赖 3. 执行构建脚本,生成 public/ 4. 把 public/ 同步到对象存储 5. 调用 CDN 接口刷新资源缓存 6. 通知我发布成功或失败这个流水线跑起来后,第三个网站才真正变成“只谈内容,不谈运维”的状态。我写了一篇文章,提交到主分支,剩下的地方平台和脚本自己跑完。偶尔构建失败,也只是因为某个模板写错了或者网络波动,查看日志定位起来都不难。
经验:把“发布”做成流水线,不只是为了省那几分钟,更是为了让过程可重复。手动上传最大的隐患是“这次和上次操作不完全一样”,某个文件漏传、缓存忘刷,问题都藏在细节里。自动化能把这种低级失误直接消灭。
4.3 域名、HTTPS 与访问加速
部署的最后一步,是把域名接入 CDN,并强制开启 HTTPS。我做了三件事:把 DNS 解析从原来的服务器地址改成 CDN 提供的加速地址;把 HTTP 请求统一 301 重定向到 HTTPS 主域名,避免出现“同一个页面有多个地址”的 SEO 问题;证书设置里开启自动续签,让浏览器小锁头永远不会过期。
对个人网站来说,HTTPS 现在已经不是可选加分项,而是底线。它直接影响浏览器地址栏的可信标识、表单数据安全,甚至影响搜索引擎对页面的评价。好在证书签发和续期早已自动化,我只需要在控制台里点一下开启,剩下的交给流程处理。
CDN 加速对国内外的访问速度提升也确实明显。以前第二个网站的服务器在某个机房,离得远的用户打开首页要转圈好几秒;现在静态文件从最近的节点返回,大部分地区都能做到“秒开”。做内容网站,速度本身就是阅读体验的一部分。
5. 上线以后:常见问题与排查经验
第三个网站上线后的第一个月,我没有急着写很多内容,而是有意识地观察它会不会出问题。这里把遇到过的和预判到的典型问题整理一下,给你做个速查。
5.1 改完文章,页面却不更新
这是静态站最经典的问题,十有八九是缓存。页面第一次被访问后,CDN 会把 HTML 缓存一段时间。即使我把文章重新构建、重新上传,缓存没过期时用户看到的还是旧版本。我的处理方法是:在脚本里,每次发布完主动刷新整站缓存;同时在本地预览后,用无痕窗口检查线上页面,确认显示的是新内容。
如果连主动刷新都还没生效,别急着怀疑平台,先看浏览器端是不是缓存了旧页面。检查 Network 面板里的响应头有没有“from disk cache”这类提示,有就说明是本地浏览器缓存,强制刷新或者等缓存过期就好。
5.2 链接失效和死链
内容多了之后,最容易出现两类链接问题。一类是我在文章里引用旧文章的地址,但旧文章后来改了 slug;另一类是图片路径写错,构建命令没发现,页面加载直接 404。我在检查脚本里加了一个“站内链接遍历器”,把 public 目录里生成的全部 HTML 扫一遍,找出指向不存在页面的链接,并逐条列出来。
这个检查一定要放进发布流程里,而不是想起来才跑一次。否则一旦形成死链,搜索引擎爬虫会不断抓到 404,对整站评价很不友好。
5.3 内容变多,构建速度变慢
大概写到几十篇文章时,我开始觉得每次构建要等的时间有点长。原因是每次构建都会重新扫描全部文章、重新压缩所有图片。解决办法有两个:一是把构建改成增量模式,只重新生成有改动的文章和受影响列表页;二是把原始图片从源码仓库里挪出去,单独放在对象存储上,不在构建时反复处理。
对于个人网站,构建时间从几秒变成几十秒也不是大问题,但提早意识到这个方向,可以避免内容多了之后再来动手术。
5.4 安全和垃圾流量
静态网站没有后台,最让动态网站头疼的暴力破解和后台扫描基本消失了。但完全零风险是不现实的,我依然做了几件小事:保证源码仓库不泄露,敏感配置通过环境变量注入而不是写死在仓库里;在 CDN 层开启一定程度的访问频率限制,防止某个 URL 被瞬间刷爆;robots.txt 里只开放应该被检索的路径,不开放任何临时目录。
对个人网站来说,“安全”的核心不是上多复杂的防护,而是别给自己留一个常年在线、没有及时升级的“活靶子”。静态化之后,靶子本身就不在了。
最后,我心里对“第三个网站”的一点新看法
如果你也在做“我的第三个网站”这类项目,我想说:真正让这个网站成功的,不是某个厉害的框架,也不是某个酷炫的特效,而是你愿不愿意半年后、一年后继续维护它、继续往里写东西。技术上的减法,换来的是内容上的加法;不需要半夜起来修数据库,才更愿意在周末坐下来写一篇长文。
我现在对这个网站最大的满意之处,是它已经不太需要“被维护”了。我打开后台的频率,远低于打开编辑器开始写新内容的频率。最后分享一个小技巧:刚开始别急着把所有功能都搭好,先把“从写文章到线上发布”这条最短路径跑顺。只要这条路径足够顺滑,后续的扩展都会很自然;如果这条路径很痛苦,前面做再多漂亮页面都留不住你。
第三个网站,对我而言终于不是建来玩的网站了,它是我真正愿意长期“住”下去的站点。