资讯详情

资讯详情

Vue 3 + Vite构建个人博客系统全攻略

1. 项目概述为什么选择从零构建个人博客系统十年前我刚入行时搭建个人博客还是件门槛很高的事。如今虽然有了WordPress这样的成熟方案但亲手实现一套博客系统仍然是前端开发者最好的练手项目。这个系统麻雀虽小五脏俱全能覆盖前端开发的完整技术链从界面设计到状态管理从Markdown解析到SEO优化甚至还能触及服务端渲染SSR等进阶领域。我见过太多简历写着精通Vue/React的候选人连个完整的博客项目都讲不清楚。这次我们就用最主流的技术栈Vue 3 Vite Node.js实现包含文章列表、详情页、标签分类、暗黑模式等标准功能的博客系统。所有代码都会开放但更重要的是理解每个技术决策背后的思考过程。2. 技术选型与架构设计2.1 前端框架抉择Vue 3的Composition API优势在2026年的前端生态中React 19和Vue 3仍是主流选择。我最终选择Vue 3的原因有三点Composition API更适合封装博客的业务逻辑比如文章点赞功能内置的Transition组件能轻松实现路由切换动画与Vite的深度集成带来极致开发体验项目基础架构如下/blog-frontend ├── public/ # 静态资源 ├── src/ │ ├── assets/ # 编译资源 │ ├── components/ # 通用组件 │ ├── composables/ # 逻辑复用 │ ├── router/ # 路由配置 │ ├── stores/ # 状态管理 │ ├── styles/ # 全局样式 │ ├── utils/ # 工具函数 │ ├── views/ # 页面组件 │ ├── App.vue # 根组件 │ └── main.js # 入口文件 └── vite.config.js # 构建配置2.2 状态管理方案对比对于博客这类轻度交互的应用Pinia比Vuex更合适// stores/articles.js export const useArticleStore defineStore(articles, () { const list ref([]) const featured computed(() list.value.filter(article article.featured)) async function fetchArticles() { const { data } await axios.get(/api/articles) list.value data } return { list, featured, fetchArticles } })注意避免在组件中直接操作store状态所有修改都应通过actions方法进行3. 核心功能实现详解3.1 Markdown渲染的完整解决方案博客系统的核心是Markdown内容渲染我们采用marked.js highlight.js方案// utils/markdownParser.js import marked from marked import hljs from highlight.js marked.setOptions({ highlight: (code, lang) { return hljs.highlightAuto(code, [lang]).value }, breaks: true, gfm: true }) export const parseMarkdown (content) { return marked.parse(content) }在Vue组件中使用时需要特别注意XSS防护template div classcontent v-htmlparsedContent/div /template script setup import { parseMarkdown } from /utils/markdownParser import DOMPurify from dompurify const props defineProps([content]) const parsedContent computed(() { return DOMPurify.sanitize(parseMarkdown(props.content)) }) /script3.2 文章列表的虚拟滚动优化当文章数量超过50篇时传统渲染方式会出现性能问题。以下是虚拟滚动实现要点template div classscroll-container scrollhandleScroll div classphantom :style{ height: totalHeight px } div v-foritem in visibleItems :keyitem.id classarticle-item :style{ transform: translateY(${item.offset}px) } !-- 文章预览内容 -- /div /div /div /template script setup import { computed, ref } from vue const props defineProps([articles]) const scrollTop ref(0) const itemHeight 120 // 每篇文章占用的固定高度 const totalHeight computed(() props.articles.length * itemHeight) const startIndex computed(() Math.floor(scrollTop.value / itemHeight)) const visibleCount computed(() Math.ceil(window.innerHeight / itemHeight) 2) const visibleItems computed(() { return props.articles .slice(startIndex.value, startIndex.value visibleCount.value) .map((article, index) ({ ...article, offset: (startIndex.value index) * itemHeight })) }) function handleScroll(e) { scrollTop.value e.target.scrollTop } /script4. 高级功能实现4.1 暗黑模式的完整实现方案现代博客的标配功能需要处理好以下细节系统偏好检测本地存储记忆过渡动画优化// composables/useDarkMode.js import { ref, watchEffect, onMounted } from vue export function useDarkMode() { const isDark ref(false) onMounted(() { // 检测本地存储或系统偏好 const savedMode localStorage.getItem(darkMode) const systemPrefersDark window.matchMedia((prefers-color-scheme: dark)).matches isDark.value savedMode ? JSON.parse(savedMode) : systemPrefersDark updateDOMClass() }) function toggle() { isDark.value !isDark.value localStorage.setItem(darkMode, isDark.value) updateDOMClass() } function updateDOMClass() { const method isDark.value ? add : remove document.documentElement.classList[method](dark) } return { isDark, toggle } }CSS变量配置示例:root { --text-primary: #333; --bg-primary: #fff; --transition-duration: 0.3s; } .dark { --text-primary: #f0f0f0; --bg-primary: #1a1a1a; } body { color: var(--text-primary); background-color: var(--bg-primary); transition: color var(--transition-duration), background-color var(--transition-duration); }4.2 基于Service Worker的离线缓存提升博客访问体验的关键技术// public/sw.js const CACHE_NAME blog-v1 const ASSETS [ /, /index.html, /manifest.json, // 其他关键静态资源 ] self.addEventListener(install, (event) { event.waitUntil( caches.open(CACHE_NAME) .then(cache cache.addAll(ASSETS)) ) }) self.addEventListener(fetch, (event) { event.respondWith( caches.match(event.request) .then(response response || fetch(event.request)) ) })在Vite中注册Service Worker// main.js if (serviceWorker in navigator) { window.addEventListener(load, () { navigator.serviceWorker.register(/sw.js) }) }5. 性能优化实战5.1 图片懒加载与自适应博客中最影响性能的往往是图片完整解决方案img v-lazyarticle.cover :data-srcset ${article.cover}?width400 400w, ${article.cover}?width800 800w sizes(max-width: 600px) 400px, 800px alt文章封面 /配套的懒加载指令// directives/lazy.js export default { mounted(el) { const observer new IntersectionObserver((entries) { entries.forEach(entry { if (entry.isIntersecting) { const img entry.target img.src img.dataset.src if (img.dataset.srcset) { img.srcset img.dataset.srcset } observer.unobserve(img) } }) }) observer.observe(el) } }5.2 关键CSS提取与预加载使用Vite插件优化首屏渲染// vite.config.js import { createHtmlPlugin } from vite-plugin-html export default { plugins: [ createHtmlPlugin({ minify: true, inject: { data: { preloadLinks: // 动态生成预加载标签 } } }) ] }手动提取关键CSS的技巧/* styles/critical.css */ .header, .hero, .first-article { /* 首屏可见区域的样式 */ }6. 部署与SEO优化6.1 静态资源部署最佳实践现代前端部署方案推荐# 生产环境构建 npm run build # 部署到GitHub Pages示例 npm install gh-pages --save-dev # package.json scripts: { deploy: gh-pages -d dist }重要提示确保正确配置404重定向到index.html以支持前端路由6.2 深度SEO优化方案博客系统的SEO关键点!-- index.html -- head title{{ blogTitle }}/title meta namedescription content{{ blogDescription }} meta propertyog:title content{{ blogTitle }} meta propertyog:description content{{ blogDescription }} meta propertyog:image content{{ coverImage }} link relcanonical href{{ currentUrl }} /head动态SEO处理使用vue-router的afterEach钩子// router/index.js router.afterEach((to) { document.title to.meta.title || 默认标题 const metaDesc document.querySelector(meta[namedescription]) if (metaDesc) { metaDesc.setAttribute(content, to.meta.description || 默认描述) } // 其他meta标签更新... })7. 常见问题与解决方案7.1 开发环境跨域问题本地开发时常见的API跨域解决方案// vite.config.js export default { server: { proxy: { /api: { target: http://your-backend-server.com, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } }7.2 路由切换滚动行为改善用户体验的路由滚动配置// router/index.js const router createRouter({ history: createWebHistory(), scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition } else if (to.hash) { return { el: to.hash, behavior: smooth } } else { return { top: 0, behavior: smooth } } } })7.3 内容闪烁问题FOUT/FOUC解决样式和内容加载不同步的问题// App.vue script setup import { onMounted } from vue onMounted(() { // 移除加载占位符 const placeholder document.getElementById(app-loading) if (placeholder) { placeholder.style.opacity 0 setTimeout(() { placeholder.remove() }, 300) } }) /script template div idapp-loading classloading-placeholder !-- 加载动画或骨架屏 -- /div RouterView / /template8. 项目扩展方向8.1 添加评论系统基于GitHub Issues的轻量级方案// composables/useComments.js export async function fetchComments(articleId) { const response await fetch( https://api.github.com/repos/yourname/yourrepo/issues/${articleId}/comments, { headers: { Accept: application/vnd.github.v3json } } ) return response.json() }8.2 实现服务端渲染SSR使用Vite SSR的简单示例// server.js import { createSSRApp } from vue import express from express import { renderToString } from vue/server-renderer import App from ./src/App.vue const server express() server.get(*, async (req, res) { const app createSSRApp(App) const html await renderToString(app) res.send( !DOCTYPE html html head titleMy Blog/title /head body div idapp${html}/div script src/client.js/script /body /html ) }) server.listen(3000)8.3 接入CMS后台推荐使用Strapi作为内容管理后端// utils/api.js export const getArticles async () { const response await fetch(https://your-strapi-instance.com/api/articles) const { data } await response.json() return data.map(item ({ id: item.id, title: item.attributes.title, content: item.attributes.content // 其他字段映射... })) }9. 完整项目结构与代码组织最终项目结构建议/blog-system ├── client/ # 前端代码 │ ├── public/ # 静态资源 │ ├── src/ # 源代码 │ └── vite.config.js # 构建配置 ├── server/ # 可选后端代码 │ ├── api/ # API路由 │ └── app.js # 服务入口 ├── docs/ # 项目文档 ├── scripts/ # 构建脚本 └── package.json # 项目配置关键依赖推荐{ dependencies: { vue: ^3.3.0, vue-router: ^4.2.0, pinia: ^2.1.0, axios: ^1.4.0, marked: ^7.0.0, highlight.js: ^11.7.0, dompurify: ^3.0.0 }, devDependencies: { vite: ^4.4.0, vitejs/plugin-vue: ^4.2.0, sass: ^1.63.0 } }10. 开发与调试技巧10.1 组件级热更新配置优化开发体验的Vite配置// vite.config.js export default { server: { watch: { usePolling: true, // Docker环境下需要 interval: 1000 } }, plugins: [ vue({ template: { transformAssetUrls: { base: null, includeAbsolute: false } } }) ] }10.2 性能分析工具使用使用Chrome DevTools进行性能审计运行生产环境构建启动本地服务器访问chrome://inspect选择Open dedicated DevTools for Node使用Performance和Memory面板分析10.3 移动端调试方案真机调试的几种方式使用ngrok暴露本地开发环境ngrok http 5173Chrome远程调试需USB连接使用BrowserStack等云测试平台11. 项目实战经验分享11.1 从设计稿到代码的转换技巧高效实现UI设计的几个要点使用CSS变量统一设计系统:root { --spacing-unit: 8px; --spacing-1: calc(var(--spacing-unit) * 1); --spacing-2: calc(var(--spacing-unit) * 2); /* ... */ }建立原子化组件库Button、Card等使用Figma插件自动生成样式代码11.2 复杂交互的状态管理文章点赞功能的完整实现// composables/useArticleLike.js export function useArticleLike(articleId) { const likeCount ref(0) const isLiked ref(false) const updateLike async () { try { const newState !isLiked.value await axios.post(/api/articles/${articleId}/like, { like: newState }) likeCount.value newState ? 1 : -1 isLiked.value newState } catch (error) { console.error(点赞操作失败, error) } } return { likeCount, isLiked, updateLike } }11.3 错误边界处理全局错误捕获方案!-- ErrorBoundary.vue -- template slot v-if!hasError / div v-else classerror-fallback h3出错了/h3 button clickrecover重试/button /div /template script setup import { ref } from vue const hasError ref(false) const recover () { hasError.value false window.location.reload() } const errorHandler (err) { hasError.value true console.error(组件错误:, err) // 可以上报错误到监控系统 } defineExpose({ errorHandler }) /script使用示例template ErrorBoundary UnstableComponent / /ErrorBoundary /template12. 项目优化进阶路线12.1 渐进式Web应用PWA集成使博客支持离线访问的关键步骤// vite.config.js import { VitePWA } from vite-plugin-pwa export default { plugins: [ VitePWA({ registerType: autoUpdate, manifest: { name: 我的博客, short_name: 博客, start_url: /, display: standalone, background_color: #ffffff, icons: [ { src: /icon-192.png, sizes: 192x192, type: image/png } ] } }) ] }12.2 性能监控接入使用Sentry进行前端监控// main.js import * as Sentry from sentry/vue const app createApp(App) Sentry.init({ app, dsn: your-dsn-here, integrations: [ new Sentry.BrowserTracing({ routingInstrumentation: Sentry.vueRouterInstrumentation(router) }) ], tracesSampleRate: 0.2 })12.3 自动化测试策略组件测试配置示例使用Vitest// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ test: { globals: true, environment: jsdom }, plugins: [vue()] })测试用例示例// tests/ArticleCard.spec.js import { mount } from vue/test-utils import ArticleCard from /components/ArticleCard.vue describe(ArticleCard, () { it(渲染文章标题, () { const wrapper mount(ArticleCard, { props: { article: { title: 测试标题, excerpt: 测试摘要 } } }) expect(wrapper.text()).toContain(测试标题) }) })13. 项目部署与持续集成13.1 GitHub Actions自动化部署基础部署工作流配置# .github/workflows/deploy.yml name: Deploy Blog on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Setup Node uses: actions/setup-nodev3 with: node-version: 18 - name: Install dependencies run: npm ci - name: Build project run: npm run build - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pagesv3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./dist13.2 Docker容器化部署生产环境Docker配置# Dockerfile FROM node:18-alpine as builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --frombuilder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]配套的Nginx配置# nginx.conf server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xmlrss text/javascript; }14. 项目维护与迭代建议14.1 版本更新策略推荐遵循语义化版本控制MAJOR版本不兼容的API修改MINOR版本向下兼容的功能新增PATCH版本向下兼容的问题修正使用npm version自动管理npm version patch -m 修复文章详情页样式问题 npm version minor -m 新增暗黑模式功能 npm version major -m 重构项目使用Composition API14.2 依赖安全更新使用npm audit定期检查漏洞npm audit npm audit fix --force配置GitHub Dependabot自动更新# .github/dependabot.yml version: 2 updates: - package-ecosystem: npm directory: / schedule: interval: weekly14.3 用户反馈收集简单的反馈组件实现template div classfeedback-widget button clickisOpen !isOpen {{ isOpen ? × : 反馈 }} /button div v-ifisOpen classfeedback-form textarea v-modelfeedback placeholder您的建议.../textarea button clicksubmit提交/button /div /div /template script setup import { ref } from vue const isOpen ref(false) const feedback ref() const submit async () { await fetch(/api/feedback, { method: POST, body: JSON.stringify({ message: feedback.value }) }) isOpen.value false feedback.value } /script15. 项目商业价值扩展15.1 广告接入方案Google AdSense集成要点script async srchttps://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?clientca-pub-XXXXXX crossoriginanonymous/script ins classadsbygoogle styledisplay:block >// utils/stripe.js import { loadStripe } from stripe/stripe-js let stripePromise export const getStripe () { if (!stripePromise) { stripePromise loadStripe(import.meta.env.VITE_STRIPE_KEY) } return stripePromise } export const createCheckoutSession async (priceId) { const response await fetch(/api/create-checkout-session, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ priceId }) }) return response.json() }15.3 数据分析集成Google Analytics 4配置// main.js import { initializeApp } from firebase/app import { getAnalytics } from firebase/analytics const firebaseConfig { apiKey: YOUR_API_KEY, projectId: YOUR_PROJECT_ID, appId: YOUR_APP_ID, measurementId: G-XXXXXXXXXX } const app initializeApp(firebaseConfig) const analytics getAnalytics(app) router.afterEach((to) { logEvent(analytics, page_view, { page_path: to.path }) })16. 项目代码质量保障16.1 ESLint配置推荐现代前端项目的ESLint配置// .eslintrc.js module.exports { root: true, env: { browser: true, es2021: true }, extends: [ eslint:recommended, plugin:vue/vue3-recommended, plugin:typescript-eslint/recommended, prettier ], parserOptions: { ecmaVersion: latest, sourceType: module }, rules: { vue/multi-word-component-names: off, vue/require-default-prop: off, vue/attribute-hyphenation: off } }16.2 Git提交规范使用Commitizen规范提交信息npm install -g commitizen commitizen init cz-conventional-changelog --save-dev --save-exact示例提交格式feat: 新增文章点赞功能 fix: 修复移动端菜单点击失效问题 docs: 更新README安装说明 chore: 更新依赖版本16.3 代码审查要点有效的Pull Request审查清单功能实现是否符合需求文档是否包含必要的单元测试代码风格是否符合规范是否有明显的性能问题是否处理了所有边界情况文档是否同步更新17. 项目文档编写指南17.1 README标准结构专业项目的README模板# 博客系统前端 [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 现代化的个人博客系统前端实现基于Vue 3 Vite构建。 ## 特性 - Markdown文章渲染 - 响应式设计 - 暗黑模式支持 - 文章分类与标签 ## 快速开始 bash # 安装依赖 npm install # 开发模式 npm run dev # 生产构建 npm run build ## 配置 复制.env.example重命名为.env并修改配置 ini VITE_API_BASE_URLhttps://api.example.com ## 部署 参考[部署文档](docs/DEPLOYMENT.md)17.2 技术文档编写技巧优秀的API文档示例## 文章接口 ### 获取文章列表 http GET /api/articles 查询参数 | 参数 | 类型 | 说明 | |------|------|------| | page | number | 页码默认为1 | | limit | number | 每页数量默认为10 | | tag | string | 按标签筛选 | 响应示例 json { data: [ { id: 1, title: 示例文章, excerpt: 文章摘要... } ], meta: { total: 100, currentPage: 1 } } 18. 项目社区建设18.1 开源项目推广有效的开源运营策略在GitHub仓库添加完整的文档编写项目介绍博客文章参与相关技术社区讨论制作项目演示视频参加黑客马拉松比赛18.2 贡献指南编写鼓励社区贡献的指南示例# 如何贡献 欢迎任何形式的贡献以下是参与步骤 1. Fork仓库 2. 创建特性分支 (git checkout -b feat/awesome-feature) 3. 提交更改 (git commit -am Add awesome feature) 4. 推送到分支 (git push origin feat/awesome-feature) 5. 创建Pull Request ## 开发规范 - 遵循现有的代码风格 - 提交信息使用[约定式提交](https://www.conventionalcommits.org/) - 新功能需包含单元测试 - 文档需同步更新18.3 问题管理策略高效处理GitHub Issues的技巧使用模板规范问题报告添加适当的标签bug, enhancement等设置里程碑管理版本计划定期清理已解决问题对常见问题整理FAQ文档19. 前沿技术探索19.1 Web Components集成在Vue中使用原生组件的示例// src/components/WebComponentWrapper.vue template div refcontainer/div /template script setup import { ref, onMounted } from vue const container ref(null) onMounted(() { const element document.createElement(my-web-component) element.setAttribute(title, 来自Vue的属性) container.value.appendChild(element) }) /script19.2 WebAssembly应用场景使用Rust编写高性能解析器// src/lib.rs #[wasm_bindgen] pub fn parse_markdown(input: str) - String { let parser pulldown_cmark::Parser::new(input); let mut html_output String::new(); pulldown_cmark::html::push_html(mut html_output, parser); html_output }前端调用方式import init, { parse_markdown } from ./markdown_parser.wasm async function run() { await init() const html parse_markdown(# Hello WASM!) document.getElementById(content).innerHTML html }19.3 人工智能辅助开发使用AI代码补全的实践GitHub Copilot的提示词技巧// 使用Pinia实现文章点赞功能 // 需要持久化到localStorage // 包含防抖逻辑ChatGPT生成测试用例使用Tabnine优化代码补全20. 职业发展建议20.1 项目经验展示技巧如何将博客项目写入简历个人博客系统Vue 3 Vite - 实现了Markdown解析、代码高亮等核心功能 - 使用虚拟滚动优化长列表性能 - 接入PWA支持离线访问 - 通过GitHub Actions实现CI/CD自动化20.2 技术博客写作建议优秀技术文章的特点明确的问题导向完整的实现步骤真实的踩坑记录可运行的代码示例清晰的配图说明20.3 开源贡献进阶路线从使用者到维护者的成长路径提交问题报告修复文档错误解决简单的bug实现新功能成为项目维护者
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →