最近,我使用 Nuxt3 全面重构了我的官网。大功告成后经实测发现,首屏加载速度其实变慢了。但奇妙的是,这次重构我却一点都不后悔。
上一个版本的官网,我是用最纯粹的 HTML+CSS+JS 开发的。说实话,刚写完的时候自己非常满意,纯原生代码,没有任何框架负担。但最近在规划我拥有的数字资产后续的功能时,我的想法发生了一些根本性的转变。我开始意识到,之前把很多工具一股脑塞进微信小程序里,其实并不合理。对于开发者或者需要高频使用不需要账号体系的工具的用户来说,在电脑大屏上操作,远比在手机上点来点去效率高得多,这也是我在小程序上实现没有多少人访问的原因吧。更别说每次改动功能就被小程序那让人头疼的、动不动就被卡住的审核机制了。于是我冒出一个强烈的念头:我要把小程序里适合在电脑上用的工具,全部搬到 Web 网页上来,面向全球用户开放;同时去精简小程序,让它只留下适合移动端且符合小程序场景的即开即用的功能。当我带着这个目标重新去审视我那套 HTML+CSS+JS 的旧代码时,我发现现在这样的写法后期维护起来极其麻烦。最让我抓狂的就是组件复用问题。在旧网站里,像导航栏和页脚这种每个页面都有的东西,要么靠人工全量复制粘贴,要么就直接缺失。每次我想改动一个导航菜单,就要把所有 HTML 文件全部手动改一遍。这种“原始人”一样的维护方式,对于我想规范化后期维护的人来说,不仅工作量巨大,而且写起来让人极度讨厌和烦躁。另一个更深层的坎是:我不希望这个官网仅仅是一个死板的展示页面。在我接下来的蓝图里,这个 Web 平台应该是一个“核心底座”——它能作为数据和逻辑的中心,向上能和小程序联通,向下能跟我的客户端工具做交互,而不是各自为战的孤岛。回看过去,这个官网前后重构了不下三次,但过去的改动大多只是 UI 层面的修修补补。而这一次,也许正是意识的苏醒,让我决定不再无谓地徘徊。为了彻底解决组件复用、多端联通以及降低未来维护成本的痛点,我最终敲定了 Nuxt3 + Naive UI + Nginx 这套架构,并决定长期坚持走下去。
- 跨端响应式的 UI 抉择,官网的 UI 必须完美兼容移动端、PC 端和平板端。以前尝试过很多 UI 框架,例如WeUI,Bootstrap等,都不尽如人意,我也不想再投入过多精力去试错。虽然我有丰富的后端管理平台 UI 开发经验,但总觉得那些框架不太适合做门户官网。我需要一款既简单易学、又能满足多端响应式需求的 UI 框架。刚好之前做客户端项目时用过 Naive UI,体验极佳,于是这次便果断选择了它。
- 确定使用 Naive UI 后,前端生态自然锁定了 Vue3。虽然原生 JavaScript 写简单页面很方便,但在实现复杂功能时,远不如 Vue3 高效。Vue3 内置了诸多便利功能,尤其是对有 Vue2 基础的我来说,上手极快。我非常享受在同一个 Vue 文件中把逻辑、页面、样式“一把刷”搞定的开发体验。
- 选定 UI 和前端底座后,我需要一个强大的开发框架来落地想法。这次重构,我计划编写一系列在线工具来替代微信小程序的部分功能,并计划停用小程序这些功能。经过改造后,我的这些工具将彻底面向全球用户开放,而不仅仅是小程序用户。在开发规范上,我更倾向于有组织、有约束的结构。经过一番调研,我发现当下流行的 Nuxt3 正好完美契合我的诉求。
为什么选择Nuxt3?
比如摆脱复制粘贴:以前使用原生开发网页,HTML 代码难以复用,缺乏组件化概念,每个页面都要复制粘贴相同的导航栏和底部,在没有框架的情况下,这些布局都需要每个页面去赋值。在 Nuxt3 中,我可以直接利用 Vue3 的 Layout(布局)机制完美解决。
状态管理与声明式渲染:纯 JS 操作 DOM 过于繁琐。对于习惯了后端逻辑的我来说,Vue3 的声明式渲染和响应式数据,写起来比纯单页面或原生 DOM 舒服太多,更符合我的思维习惯。
约定式路由:不需要手动配置复杂的路由表。在 Nuxt3 中,建一个文件就是一条路径,一个 Vue 文件就是一个页面,极大地简化了功能开发。
减轻心智负担:以前作为后端开发者,还要兼顾 UI 的微调和重构,心智负担极重。现在有了 Naive UI 的加持,再也不用为样式发愁,整个人轻松了很多。
SSR(服务端渲染)与 SSG(静态生成):在保留现代前端丝滑开发体验的同时,完美解决了官网的 SEO 问题。配合 Nginx 的高效反向代理与静态资源缓存,让整体架构非常稳健。
真正的前后端分离:前端逻辑高度模块化,UI 变更不再需要频繁折折腾后端的模板渲染,职责划分清清楚楚。
重构比想象更顺利
这次重构我还计划新增在线小工具,用 Web 网页替代小程序原有的部分功能,这些工具对开发者而言,在电脑端操作远比在手机上方便。而且,Web 端完全没有小程序的审核限制。这是解决小程序部分工具的痛点。
在线工具构思实现
在真正动手开发这些工具时,我曾面临一个架构设计上的抉择:是采用“一个页面一个工具”的松耦合模式,还是做一个“一页包揽所有工具”的动态配置中心?如果选择后者(一页包揽所有),前端只需要做一个万能模板,剩下的全靠后端下发一个巨大的 JSON 配置文件,前端拿到 JSON 后再去动态渲染,这是我最擅长的地方,因为我懂后端。但我反复推演后,最终坚定地选择了“一个页面一个工具”。
我是基于以下几点现实考量:
- 布局的定制化需求:在线工具不是千篇一律的FORM表单。Base64 编解码可能需要左右两个大文本框,时间戳互转需要一排输入框,而哈希计算可能需要文件拖拽区域。如果纯靠后端拉取 JSON 配置来渲染,为了兼容各种布局,前端组件会写得极其臃肿,最后不仅不美观,反而画地为牢。
- 逃离 JSON 维护的地狱:很多人以为用 JSON 驱动很高级,但实际上,后期维护一个庞大、复杂的 JSON 文件,心智负担比写代码还要重。一旦配置错了一个逗号或键名,整个工具箱可能直接崩溃。
- 高内聚与低耦合:采用“一个页面一个工具”,想增加新功能,我只需要新建一个 .vue 文件。每一个工具都是独立的沙盒,有它自己最舒服的布局和业务逻辑,互不干扰。虽然从文件数量上看,页面变多了,但它带来了无与伦比的低心智工作量和高扩展性。未来如果某个工具要调整视觉或者增加特殊功能,我直接去改对应的页面即可,完全不用担心引发多米诺骨牌效应。

如上是在线工具的文件组织,目前我已经实现了 Base64 编解码、URL 编解码、MD5/SHA256 哈希计算、时间戳与日期互转等功能。在 Nuxt3 中,这些工具可以非常轻松地抽象为独立的 Page(页面),未来我新增工具仅需要新增一个vue文件即可。
其中的一个工具示例,比如MD5哈希:
<template>
<div class="page-container">
<div style="max-width: 900px; margin: 0 auto;">
<n-card title="哈希计算 (MD5)" segmented>
<n-space vertical size="large">
<n-input v-model:value="input" type="textarea" placeholder="请输入原文内容..."
:autosize="{ minRows: 3 }" />
<n-space justify="center">
<n-button type="primary" :loading="loading" @click="handleCompute('md5')">
生成MD5
</n-button>
<n-button v-if="output" type="info" ghost @click="copyToClipboard">
复制结果
</n-button>
</n-space>
<n-input v-model:value="output" type="textarea" readonly placeholder="结果将显示在这里..."
:autosize="{ minRows: 3, maxRows: 6 }" />
</n-space>
</n-card>
<!-- 下部 Markdown 说明 -->
<n-card title="工具说明" style="margin-top: 24px;">
<div class="markdown-body" v-html="markdownContent"></div>
</n-card>
</div>
</div>
</template>
<script setup>
import {
ref
} from 'vue'
import {
marked
} from 'marked'
const message = useMessage() // 实例化消息对象
const {
copy
} = useCopy()
const input = ref('')
const output = ref('')
const loading = ref(false)
const handleCompute = async (type) => {
if (!input.value.trim()) {
message.warning('请输入内容')
return
}
loading.value = true
try {
const res = await toolService.hashAction(input.value, type)
output.value = res
message.success(`操作成功`)
} catch (err) {
message.error(err.message || '处理失败,请检查输入或后端服务')
console.error(err)
} finally {
loading.value = false
}
}
// 一键复制功能
const copyToClipboard = () => {
if (!output.value) return
copy(output.value)
}
import rawText from '~/assets/md5.md?raw'
const markdownContent = computed(() => marked.parse(rawText))
useHead({
title: 'MD5/SHA256 在线计算'
})
</script>
在这个页面里,藏着几个知识点。这次重构不仅打通了底层,在具体的编码实现上,Nuxt3 的很多特性也深得我心,甚至完美契合了我作为一个后端开发者的强迫症:
统一封装逻辑,拒绝代码散落。在实现 MD5/SHA256 哈希计算、Base64 编解码这些工具的核心算法时,我没有把它们零散地写在各个页面里,而是统一封装进了 services/tools.ts 文件中。这可能是我根深蒂固的后端思维在起作用——我极度喜欢把核心业务逻辑集中存储和管理,而不是像以前写纯 JS 那样分散在各个文件里。前端页面只需要负责调用,底层逻辑安安静静地待在自己的服务层(Services),这种职责分离让代码清晰得像一件艺术品。
Markdown 渲染说明,彻底解放审美。工具做好了,总得给用户写个“使用说明”吧?以前最头疼的就是为了写个说明,还要去调各种 HTML 标签和 CSS 样式。这次我直接引入了 Markdown 来编写使用说明。我觉得这招简直太绝了!把写好的 .md 文件直接渲染出来,根本不用操心任何样式,默认就非常美观和规范。 这种把内容和表现分离的方式,不仅极大地提高了生产力,也让整个网站的工具说明保持了高度一致的视觉体验。
用 useHead 搞定定制化的 SEO。在组件化开发中,最怕的就是用了全局 Layout(布局)之后,所有页面的标题和关键词都变成了一模一样的。而 Nuxt3 内置的 useHead 组合式函数彻底惊艳到了我。它允许我在每个工具页面里,单独且自定义该页面的 Meta 标题和 SEO 关键词。这意味着,我既能享受全局通用布局带来的便捷,又能让每个独立工具都拥有专属的 SEO 身份证,对全球搜索引擎的抓取极其友好。
真正精妙的“神来之笔” composables 机制。如果说前面几点让我觉得用得顺手,那么 Nuxt3 的 composables(组合式函数自动导入)机制,简直让我直呼“神来之笔”!作为在线工具箱,每个页面几乎都有一个高频的刚需功能——“一键复制结果到剪贴板”。如果换作以前,我可能要在每个工具页面里都写一遍复制逻辑,或者引入一大堆臃肿的第三方库。这次我直接在 composables/useCopy.ts 里封装了复制逻辑。最让我惊艳的是,我不仅把纯文本复制的逻辑写进去了,甚至还把 Naive UI 的 message(消息提示)组件直接塞进了这个组合式函数里!现在,我的工具页面想要实现复制功能,只需要极其优雅地写上一句引用
copy。当用户点击复制时,逻辑自动执行,屏幕上方还会自动弹窗提示“复制成功”。我的工具页面连消息提示的代码都省了! 这种全局自动导入、高度内聚且随取随用的体验,把代码复用做到了极致,写起来实在是太解压、太痛快了。
最后,我希望未来将以官网为中心打造生态圈,所以易扩展是非常重要的一个特性。这一次重构,是一场“谋划已久”的蓄力。借由这次的实用场景,我终于把 Web 工具平台的底层彻底打通。后期,我还会继续添加自己高频刚需的在线功能,并且实现Web平台与小程序互动,Web平台与客户端互动,以及Web平台与嵌入式固件互动。只有便于维护的架构,才能支撑起不断膨胀的梦想。