ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从零到一搭建第二个网站:Vite+Vue3+Markdown实现轻量独立博客

2026/9/20 3:35:00 拓冰建站 浏览量
从零到一搭建第二个网站:Vite+Vue3+Markdown实现轻量独立博客 做第一个网站的时候我一度觉得自己什么都懂了。买个域名、装个WordPress、套个主题、把插件像贴膏药一样往上糊数据在后台灌了两篇文章就宣布“正式上线”。结果呢半年后一看统计面板来的全是垃圾流量后台登录页被机器人扫了几万次数据库字段被塞得乱七八糟连备份都没有。最后只能一键删除当作无事发生过。所以当我说“我的第二个网站”的时候我更愿意把它理解成一次重新做人这一次不追花活不图炫技用一套自己能完全控制的轻量方案把一个真正持续运转的独立站点搭起来。这篇文章就把整个从复盘、规划、技术选型到开发、部署、运维的过程完整写下来给你当个参照。如果你正打算做自己的个人博客、作品集或者独立小项目尤其是不想继续在“模板插件”里打转这篇文章应该能帮你少走一大截弯路。1. 失败复盘第一个网站到底死于哪里1.1 第一次建站的典型死法我当年的第一个网站本质上是“毕业设计后遗症”。心理活动特别典型想做一个个人博客因为看到别人都有觉得自己也该有。当时衡量一个网站“好不好看”的标准特别原始就是首页够不够炫、能不能自动播放BGM、滚动的时候有没有飘来飘去的特效。于是我从模板站扒了一个花花绿绿的主题然后在后台瞎折腾为了显示浏览量和文章热度装了插件为了评论区好看装了插件为了“SEO优化”一口气装了三个插件。主题更新了直接覆盖改坏了就重新上传。那会儿完全不懂静态资源缓存也不知道数据库会产生多少垃圾表只知道总有一天会出问题。直到有天打开首页被跳转到一个莫名其妙的小广告页才意识到网站被挂了马而我的“备份”还停留在安装那天。回头反思这个死法一点都不冤枉。它的问题不在技术上而在认知上。第一个站点没有回答三个基本问题给谁看解决什么问题凭什么比别人做得久1.2 从废墟里挖出来的四条教训第一需求要先于功能。我连目标读者是自己的学习笔记还是想吸引外部访客都没想清楚就急着装修门面。做出来的东西既不像日记本也不像内容平台最后谁都没有服务好。第二内容才是一个网站存在的理由技术只是传送带。你去逛书店会因为书架是红木的就多买两本书吗不会。但建站这件事特别容易让人本末倒置花两周调字体却不肯花两小时写一篇正经推荐文章。第三没有数据眼的网站等于瞎转悠。第一个网站我直到删除都没认真看过一次流量日志不知道用户从哪里来、看了什么、在哪一步离开。没有数据反馈所谓的优化全是自high。第四可迁移和可备份是最后的安全底线。网站可以丑、可以慢但数据绝不能轻易丢。我后来意识到内容应该和展示层解耦最好用纯文本格式管理而不是躺在某个私有数据库里。1.3 把“不做什么”写进方案里做第二个网站前我特别列了一个“不做清单”。不用内容管理系统、不做用户注册、不做评论系统、不装统计插件的全家桶、不在首页放轮播图。这些决定很大程度上参考了第一次失败的经验。评论区看着热闹但垃圾评论和验证码的维护成本远超它带来的价值用户注册更是个坑密码重置、权限管理、隐私合规每一项都是无底洞。我建议你也试试这种方法先写下绝不做什么再写要做什么。它会让你的第二个项目清爽得多。2. 从零规划第二个网站的产品定义与技术选型2.1 先给它一个有边界的定位第二次起步前我强迫自己把网站定位写成一句能念出来的话一个记录前端开发与独立开发实践的实验博客目标读者是两三年经验的技术同行。这句话约束了很多后续决定。既然读者是同行那就别写基础教程凑数既然面向技术人群设计上可以保持克制、极简明确传递信息优先的调性。功能范围也被压缩得很小。核心功能只有四块首页展示最近文章与简介、文章列表页、文章详情页、关于页。其中关于页放自我介绍和项目链接。归档、标签、搜索属于加分项我放在第二轮迭代做。这样做的目的是让第一个可运行版本尽快上线而不是在完美主义的泥潭里挣扎。2.2 信息架构与页面草图在代码之前我先用纸笔把站点的信息结构画了出来。首页的布局像一封自荐信第一屏放一句“我是谁、这个站在写什么”下面按时间倒序列出最近文章每篇只给标题、日期和摘要。文章详情页顶部是标题和元信息中间是正文底部留一个“上一篇/下一篇”的固定导航。关于页是一篇个人介绍外加联系方式。这套结构朴素到几乎没有任何创意但它足够清晰。不要让用户动脑思考“我现在在哪里、还能去哪里”这是小网站唯一需要遵守的导航铁律。2.3 技术选型为什么放弃了现成方案在“第二个网站”的技术选型上我认真对比过三条路线。WordPress可以继续用但我不需要动态渲染、不需要后台编辑器、更不想每月给插件和主题升级打补丁。对于一个小型内容型站点动态方案带来的全是维护负担。Hexo这类现成静态博客生成器也是一个选项。它的好处是开箱即用主题也多。但问题在于一旦你想深度定制页面逻辑就得去读别人封装好的主题源码这种定制成本往往比从零开始写一个简单的还要高。最终我选的是Vite Vue3 Tailwind CSS自建静态站配合Markdown文件管内容。这个选择说实话是带着一点私心的。我本身做前端开发这套组合在我的技能射程内出了问题可以迅速排查。而且Vite的构建链路很干净内容直接用Markdown存Git仓库所有的数据都在自己手里。页面交互也很轻不需要复杂的服务端渲染或者客户端数据请求构建时全部生成静态HTML就够用了。选型背后有一个核心决策标准在起作用每一个环节都应该是我能解释清楚的。不管是路由、构建、部署还是数据展示出了问题都能追溯到根因而不是在一层又一层封装后面抓瞎。3. 核心开发实战内容管道、页面渲染与性能优化3.1 项目初始化与关键依赖我用Vite创建项目后装了这些核心依赖vue-router用于处理前端路由、tailwindcss用于原子化样式、markdown-it负责把Markdown文本解析成HTML、highlight.js负责代码高亮。内容层面没有再引入复杂的富文本编辑器写作就在VS Code里完成比什么编辑器都顺手。初始化命令不复杂npm create vitelatest my-second-site -- --template vue cd my-second-site npm install npm install vue-router4 markdown-it highlight.js装完Tailwind后它会扫描你的模板文件把用到的类名编译成最终的CSS。这种按需生成的方式让最终样式表体积非常小首次加载自然就快。3.2 目录结构与内容组织项目结构上我刻意让内容与代码分离my-second-site/ ├── content/ │ └── articles/ │ ├── 2024-01-12-hello-world.md │ └── 2024-02-03-build-your-second-site.md ├── src/ │ ├── components/ │ ├── views/ │ │ ├── Home.vue │ │ ├── ArticleList.vue │ │ └── ArticleDetail.vue │ ├── data/ │ │ └── articles.js │ └── main.js ├── public/ │ ├── favicon.svg │ └── robots.txt └── index.html每篇Markdown文件的开头都带一份YAML格式的前置信息用来记录标题、发布日期、标签和摘要。文章的正文完全靠H2、H3组织代码块标好语言类型。这样的好处是内容源文件干净、无格式包袱而且可以直接用Git做版本管理每次修改都有历史记录。3.3 用 import.meta.glob 打通内容链路Vite提供了一个非常便利的API叫import.meta.glob可以一次性把目录下的所有Markdown文件批量加载进来。配合自定义解析逻辑就能把“内容管理”从数据库问题简化成“文件读取问题”。核心思路是这样的// src/data/articles.js import { marked } from marked; const modules import.meta.glob(/content/articles/*.md, { query: ?raw, import: default, eager: true }); export const articles Object.entries(modules).map(([path, raw]) { const { data, content } parseFrontmatter(raw); const html marked(content, { breaks: true }); return { slug: path.split(/).pop().replace(.md, ), ...data, html }; });eager: true的意思是构建时同步读取所有内容而不是运行时异步请求。这样在打包阶段所有文章就变成静态字符串嵌入到产物里了。访问页面时无需请求任何接口直接渲染。解析前端信息时我用了一个简单的函数去切分---之间的YAML块用yaml包把它转成对象逻辑很直接。之所以这么做而不是去搞一个现成的Static Site Generator是因为我只需要几十篇文章完全没必要再引入一层抽象。代码量增加不到一百行但整个内容链路都在自己掌控之中。3.4 路由、页面组件与代码分割文章列表页负责按时间倒序渲染所有文章。详情页则通过路径参数找到对应的文章对象然后用v-html把转好的HTML插入模板。这里要记得给v-html渲染出来的内容定义一套Markdown样式否则代码块、标题、段落都是一团乱麻。因为文章详情页内容较大我在路由配置里给详情页做了懒加载const ArticleDetail () import(../views/ArticleDetail.vue);这样访客打开首页时详情页的代码不会被加载只有真正点进文章时才请求对应脚本。对于静态站来说这不算什么高深的优化但确实能切切实实减少首屏流量移动端体验提升很明显。另一个值得注意的细节是图片懒加载。文章里的配图统一加loadinglazy属性同时给img标签显式设置宽高防止页面滚动时布局跳变。如果图片本身尺寸很大我还会在上传前用工具压缩一遍。我见过太多小网站被几张几兆的图片拖垮首屏。3.5 渲染性能之外的体验细节构建产物还有一个容易被忽视的点页面标题和描述。我在每个页面的useMeta或document.title设置里动态拼出“文章标题 – 网站名”的格式。文章详情页的摘要字段也写入meta namedescription。这些看似不起眼的细节后面在搜索引擎收录时价值就体现出来了。配色和字体上我没有引入任何前端UI框架就用Tailwind的默认色板加一个自定义主色。字体优先选择系统字体栈不加载远程字体文件。虽然少了一些“设计感”但它换来了极快的加载速度。4. 部署与发布自动化构建、Nginx 与 HTTPS4.1 服务器和域名的准备工作第二个网站上线前我买了一台轻量云服务器配置不需要高1核2G内存对一个纯静态站来说绰绰有余。系统装的是Debian干净稳定。SSH连接后我第一件事不是装环境而是修改SSH默认端口、禁用密码登录、生成密钥对这些基础安全措施越早做越好。域名解析上在域名服务商的后台把主域名和www子域都添加为A记录指向服务器IP。等DNS生效后再用ping或者在线工具确认解析结果。4.2 用 GitHub Actions 打通发布链路我的代码托管在GitHub上每次推送都会自动触发构建和部署。发布链路分三步先在云服务器上构建产物然后通过rsync同步到Nginx站点目录最后重新配置证书。为了传递服务器登录信息我把IP、用户名和SSH私钥都存进GitHub仓库的Secrets变量里不在配置文件里明写任何敏感信息。具体的workflow文件大致长这样name: Deploy on: push: branches: [main] jobs: build-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 - run: npm install - run: npm run build - name: Deploy via rsync uses: burnett01/rsync-deployments7.0.1 with: switches: -avzr --delete path: dist/ remote_path: /var/www/my-second-site remote_host: ${{ secrets.HOST }} remote_user: ${{ secrets.USER }} remote_key: ${{ secrets.SSH_PRIVATE_KEY }}注意--delete参数很关键它会清空服务器上的旧产物再同步新文件避免删掉的文章残留过期的HTML页面。这个deployment action用起来顺手它会自动处理SSH秘钥配置。4.3 Nginx 配置解决前端路由与缓存问题因为是纯静态站Nginx配置比动态应用简单太多。但有个坑必须处理Vue Router使用history模式路径形如/articles/slug如果服务器没做rewrite用户直接刷新这个地址会返回404。我用的配置片段是这样的server { listen 80; server_name example.com www.example.com; root /var/www/my-second-site; index index.html; location / { try_files $uri $uri/ /index.html; } location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2)$ { expires 30d; add_header Cache-Control public, immutable; } }try_files的意思是如果请求的文件不存在就回退到index.html由前端路由接管。静态资源单独配置了长缓存文件名里带了hash不用担心缓存不更新的问题。4.4 用 acme.sh 给网站免费加上 HTTPS一个现代网站没有HTTPS会被浏览器直接打上“不安全”的标签SEO也会受影响。我用acme.sh签发Lets Encrypt的免费证书过程很简单curl https://get.acme.sh | sh acme.sh --issue -d example.com -d www.example.com --webroot /var/www/my-second-site证书签发后acme.sh会自动安装证书到指定目录并在证书快要过期时自动续期。我只需要在Nginx配置里加上SSL证书路径并开启80端口跳转443listen 443 ssl http2; ssl_certificate /etc/nginx/ssl/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/example.key;上线后我习惯用在线工具扫一遍HTTPS配置检查证书链是否完整、是否支持TLS1.2以上。这一步别偷懒很多浏览器的兼容性问题都是证书链缺了中间证书导致的。4.5 发布后的即时验证清单第一次部署完成后我在服务器上依次验证这些内容首页正常打开、通过文章链接进入详情页、刷新详情页不404、控制台没有红色报错、静态资源加载完整、移动端样式正常。确认无误后才算真正“上线”。5. 上线不是终点数据、SEO、安全与持续迭代5.1 用数据做出下一轮判断我在页面里接入了一款轻量统计脚本目的是看“真实访客从哪里来”。每天早上我会扫一眼几个核心指标独立访客数、来源渠道、热门文章、平均停留时间。因为这些数据能直接反映内容产出的方向是否正确而不是靠猜。比如有段时间我写了几篇Vite实战相关的文章发现访问量明显高于其他内容来源大部分是搜索关键词。这说明这个方向有需求后续我会倾斜更多精力在这个主题上。如果没有数据反馈我可能还在写一些孤芳自赏的冷门话题。5.2 SEO 收录与站点内容分发小网站同样需要SEO但不建议走“堆关键词”那种野路子。我做的主要是三件基础事保证每篇文章URL简洁、可读性强例如/articles/auto-deploy-site而不是/post?id123生成sitemap.xml并在搜索引擎站长平台提交用robots.txt给爬虫指路屏蔽后台和静态资源目录。sitemap我写了一个小脚本构建时扫描content/articles下的文件自动生成XML。这样一来每次发布新文章后爬虫都能更快发现新页面。文章中我还习惯在文首、文末添加站内相关文章的链接帮用户延伸阅读的同时也把权重导到其他页面。5.3 安全加固清单按性价比排序安全不用做得很复杂但底线不能漏SSH方面我已经改为密钥登录并且禁用了root直接登录用小权限用户处理部署。防火墙只放行SSH、HTTP、HTTPS三样其他端口一律关闭。Nginx层面开启limit_req限制单IP的请求频率防止简单的CC攻击。文件权限做好隔离网站目录对部署用户只读写权限只给需要上传的临时目录。内容自动备份实际上文章全在Git仓库服务器上只需要定期备份Nginx配置、证书和网站目录即可。这几项加起来不用半天就能配完但对于个人站来说安全水位已经足够。5.4 常见问题与排查技巧实录运行几个月后我积累了一些很有代表性的问题和排查方法现象原因解决方式刷新文章页变成404前端路由使用history模式但Nginx未配置兼容加上try_files $uri $uri/ /index.html;HTTPS证书没有自动续期acme.sh安装时未指定webroot路径或域名解析有问题重新检查acme.sh定时任务和域名解析手动执行续期测试构建时Markdown解析报错特殊符号被误识别为HTML标签或Markdown语法检查原文对无法识别的字符用转义或者代码块处理更新文章后线上没变化常见于忘了触发部署流程或rsync失败查看GitHub Actions日志检查SSH密钥和远程路径配置页面加载慢但体积很小可能是图片没压缩或引用了远程字体库本地压缩图片改为系统字体栈还有一个小技巧值得分享遇到页面异常时先按F12打开控制台看Network里的请求。大部分问题都能从请求状态码里找到线索比瞎改代码高效十倍。结尾第二个网站走到今天对我来说最大的收获不是学会了怎么写Vue组件也不是把Nginx配置倒背如流而是真正理解了一个网站从“做出来”到“养起来”需要付出的持续心力。它像一间小书店开张只是起点之后的选书、摆位、维护门面、招呼常客才是日复一日的正事。如果你也正在规划自己的“第二个网站”我建议你把前面那些踩坑经历当成自己的预防针该省的省、该绕的绕把注意力放到内容和长期运转机制上。如果你已经做过一个网站不妨在评论区讲讲你从第一个站里学到了什么说不定你会突然发现很多人的教训其实惊人地一致。