
1. 项目概述一个现代站点导航器的诞生最近在整理自己的浏览器书签和常用工具链接时我又一次陷入了混乱。收藏夹里塞满了各种项目文档、在线工具、技术博客和设计资源每次想找一个特定的网站都得在层层文件夹里翻找半天。这让我想起了早年互联网的“网址导航站”但那些页面往往充斥着广告设计过时而且无法满足我个性化、跨设备同步的需求。于是我决定自己动手构建一个私有的、现代化的站点导航器。这就是yandong2023/site-navigator项目的初衷。简单来说这是一个自托管的个人或团队专用导航页面。你可以把它想象成一个高度定制化的浏览器主页上面整齐地排列着你所有常用的网站链接按类别分组支持搜索并且拥有清爽现代的界面。它不只是一个链接集合更是一个提升工作效率和数字生活秩序的工具。无论你是开发者、设计师、学生还是任何需要频繁访问固定网络资源的角色这个项目都能帮你把散落各处的“数字入口”集中管理起来实现一键直达。这个项目适合所有对效率有追求且具备基本命令行操作能力的用户。你不需要是前端专家因为它的部署和配置过程已经尽可能简化。接下来我将从设计思路、技术实现、部署细节到日常使用技巧完整地拆解这个项目分享我是如何从零搭建并持续优化它的。2. 核心设计思路与架构选型2.1 需求分析与设计目标在动手写第一行代码之前我明确了这个导航器需要满足的几个核心需求这直接决定了后续的技术选型。首先极致的简洁与速度。作为高频使用的入口页面加载必须飞快界面必须直观不能有任何拖泥带水的感觉。这意味着要避免重框架采用原生技术或轻量级方案。其次完全的数据自主与控制权。所有网站数据必须存储在我自己可控的地方最好是纯文本或结构化文件方便版本化管理用Git、手动编辑和跨设备同步。我不希望依赖任何后端数据库或第三方服务那样会增加复杂性和维护成本。第三响应式设计与跨平台体验。这个导航页面需要在桌面的大显示器、笔记本、平板甚至手机上都能良好显示和操作。图标和布局要能自适应不同的屏幕尺寸。第四便捷的搜索功能。当收录的站点越来越多时通过分类查找可能还不够快一个全局的、即时的站点搜索功能是必须的。最后易于部署与维护。我希望它能够以最少的依赖运行最好能静态部署在任何支持托管HTML/JS/CSS的服务器上甚至是本地文件系统直接打开也能用。基于这些目标我排除了使用像 WordPress 这类动态CMS的方案也暂时放弃了需要Node.js实时后端服务的复杂应用。思路很清晰构建一个静态单页面应用SPA所有数据在构建时生成运行时无需后端接口。2.2 技术栈决策为什么是Vite Vanilla前端框架选择上React、Vue、Svelte 都很强大但为了极致的轻量与“无框架”理念我最终选择了Vanilla JavaScript原生JS配合Vite作为构建工具。为什么不直接用三大框架对于一个核心功能是渲染列表和实现搜索的导航页来说框架带来的抽象和运行时开销显得有些“杀鸡用牛刀”。Vanilla JS 直接操作 DOM在如此小规模的应用中性能是最优的打包后的体积可以压缩到极小最终项目主JS文件仅几十KB。而且没有框架的约束代码结构更直接任何有一定前端基础的人都能轻松看懂和修改。为什么选择Vite虽然不用框架但现代前端开发离不开模块化、热更新HMR和优化构建。Vite 完美地满足了这些需求。它基于原生ES模块启动速度极快为原生JS/TS项目也提供了开箱即用的支持。它的构建流程能将我们的CSS、静态资源进行优化并轻松集成我们需要的工具链比如下面会提到的图标处理。数据存储方案纯JSON文件站点数据名称、URL、图标、分类等被存储在一个单独的sites.json文件中。这个文件结构清晰易于阅读和编辑。通过Vite的构建流程我们可以在开发时动态加载它在构建时将其部分数据内联或打包确保运行时无需发起额外的网络请求来获取数据实现瞬间加载。图标方案SVG Sprite与自动处理网站图标Favicon的获取和显示是个挑战。我采用了混合方案首选内置SVG图标对于像 GitHub、Google、YouTube等知名网站我直接内置了其品牌的SVG图标保证清晰度和可控性。备用 Favicon 抓取对于其他网站项目会尝试通过一个简单的服务或构建脚本获取其 favicon.ico。为了简化我最初实现了一个在构建时运行的Node脚本通过node-fetch获取图标并转换为Base64或存入本地避免运行时跨域问题。但在最终版本中为了部署的纯粹静态化我更多地依赖了内置图标库。搜索功能客户端全文检索搜索是纯客户端实现的。我在页面初始化时将sites.json中的数据加载到内存中构建一个简单的搜索索引通常就是遍历数组对名称、URL、分类等字段进行小写化和分词预处理。当用户输入时使用filter()和includes()等方法进行实时匹配并高亮显示结果。这种方式完全无需服务器参与响应速度极快。3. 项目结构与核心代码解析让我们深入项目文件夹看看具体的代码是如何组织并工作的。3.1 目录结构site-navigator/ ├── public/ # 静态资源构建后输出目录也可放原始资源 │ └── icons/ # 存放所有SVG图标文件 ├── src/ │ ├── assets/ │ │ └── sites.json # 核心数据所有导航站点定义 │ ├── css/ │ │ └── style.css # 项目主样式文件 │ ├── js/ │ │ ├── main.js # 应用主逻辑渲染、搜索、交互 │ │ └── utils.js # 工具函数如图标处理、数据格式化 │ └── index.html # 应用主入口HTML文件 ├── package.json # 项目依赖和脚本定义 ├── vite.config.js # Vite构建配置 └── README.md # 项目说明文档这种结构非常清晰。src里是我们的源代码public通常放不需要Vite处理的静态文件但图标为了管理方便我放在了src/assets/icons由Vite处理资源路径。3.2 数据格式定义 (sites.json)这是项目的“心脏”。一个良好的数据结构设计能让后续的渲染和搜索事半功倍。[ { name: GitHub, url: https://github.com, icon: github.svg, category: 开发, description: 代码托管平台, tags: [git, 代码, 开源] }, { name: MDN Web Docs, url: https://developer.mozilla.org, icon: mdn.svg, category: 文档, description: Web技术权威文档, tags: [前端, 文档, JavaScript] }, // ... 更多站点 ]字段解析name和url是核心。icon: 对应src/assets/icons/目录下的SVG文件名。Vite在构建时会处理这些路径。category: 用于分组显示。我会根据这个字段自动生成分类筛选器。description和tags: 这两个字段是搜索功能的关键。用户可能不记得“MDN”的全称但可能会搜“前端文档”或“JavaScript参考”tags字段提供了灵活的搜索关键词。注意在sites.json中url字段务必包含完整的协议头https://。这不仅是为了规范也是为了避免在页面中通过a标签跳转时可能被当作相对路径处理导致跳转错误。3.3 核心渲染逻辑 (main.js)主逻辑模块负责读取数据、渲染列表、处理搜索和交互。我采用了模块化的原生JS写法。1. 数据加载与初始化// 使用 Vite 的 import.meta.glob 或 fetch 来获取数据 // 在开发环境下可以直接 import为了构建优化这里使用 fetch。 let siteData []; async function loadSiteData() { try { const response await fetch(/src/assets/sites.json); siteData await response.json(); initApp(); } catch (error) { console.error(Failed to load site data:, error); // 可以在这里显示一个友好的错误提示给用户 document.getElementById(app).innerHTML pFailed to load navigation data. Please check the console./p; } }2. 渲染分类与站点卡片initApp函数首先遍历所有数据提取出唯一的分类category生成分类筛选按钮。然后根据当前选中的分类默认为“全部”过滤siteData并动态生成站点的HTML卡片。卡片生成函数createSiteCard是关键。它创建一个a标签作为卡片容器内部包含一个图标img和站点名称span。图标路径通过Vite的new URL()语法进行解析确保在开发和构建后都能正确找到资源。3. 实现即时搜索为搜索输入框绑定input事件监听器。在事件处理函数中获取搜索关键词去除首尾空格并转为小写。遍历siteData对每个站点的name、description和tags数组进行匹配检查。使用filter()得到匹配的站点数组。清空当前站点容器用匹配的结果重新渲染卡片。同时高亮显示匹配到的分类。搜索算法的核心是一个简单的字符串包含检查但对于几百个站点来说这已经足够快。如果需要支持更复杂的模糊搜索或拼音搜索可以考虑引入Fuse.js这样的轻量级库但当前方案保持了零依赖。3.4 样式设计与响应式 (style.css)样式上采用了CSS Grid布局来排列站点卡片这使得实现响应式变得非常优雅。.site-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 1.5rem; padding: 1rem 0; } media (max-width: 768px) { .site-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 1rem; } } media (max-width: 480px) { .site-grid { grid-template-columns: repeat(3, 1fr); /* 小屏幕上固定为3列 */ } }grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));这行代码是精髓。它告诉浏览器“尽可能多地创建列每列的最小宽度是140px最大是1等分1fr。如果容器宽度不够放新的一列就把已有的列拉宽一点填满空间。” 这样从宽屏到窄屏布局都能自动适应卡片数量也会动态调整。视觉设计要点采用深色主题作为默认减少长时间使用的视觉疲劳并突出卡片色彩。卡片使用box-shadow和轻微的border-radius营造层次感悬停:hover时有上浮阴影和颜色变化提供清晰的交互反馈。图标使用filter: brightness(0.95)进行统一微调使不同来源的图标在视觉上更和谐。4. 开发、构建与部署全流程4.1 本地开发环境搭建首先确保你安装了 Node.js版本16或以上和 npm或 yarn、pnpm。获取项目代码git clone https://github.com/yandong2023/site-navigator.git cd site-navigator安装依赖npm install # 或使用 yarn / pnpm启动开发服务器npm run dev执行后Vite会启动一个本地开发服务器通常地址是http://localhost:5173。打开浏览器访问你会看到一个实时的、支持热更新的导航页面。此时修改sites.json、style.css或main.js页面都会即时刷新。4.2 如何添加和管理你的站点这是最常用的操作。所有的修改都在src/assets/sites.json文件中。添加一个新站点在sites.json的数组末尾注意JSON格式的逗号新增一个对象。例如添加一个设计资源站{ name: Unsplash, url: https://unsplash.com, icon: unsplash.svg, category: 设计, description: 免费高质量图片库, tags: [图片, 摄影, 素材] }准备图标去该网站的官方页面找到其SVG格式的Logo通常在页头或开源设计资源中。下载后将其重命名为unsplash.svg放入src/assets/icons/目录。SVG图标体积小、无损缩放是最佳选择。如果找不到SVG可以暂时用一个占位图标或者使用在线工具将PNG转换为SVG。编辑后保存保存sites.json文件。由于Vite的热更新功能你的开发服务器页面会自动刷新新站点卡片会立刻出现在对应的分类下。实操心得图标管理维护一套完整的SVG图标库起初有点繁琐。我的经验是建立一个icons/目录的备份。当需要在新设备上搭建环境时可以直接复制整个图标文件夹。另外对于非常小众的网站如果找不到合适的图标可以考虑使用其首字母或一个通用符号作为替代保持界面整洁比追求完美图标更重要。4.3 构建生产版本当站点配置完毕准备部署到线上时需要构建优化后的生产版本。npm run build这个命令会触发Vite的构建流程代码优化压缩JavaScript和CSS移除未使用的代码Tree-shaking。资源处理将sites.json等静态数据打包进最终产物或复制到输出目录。SVG图标等资源会被处理并放入assets子目录并带有哈希文件名以实现长期缓存。生成输出最终产物会生成在dist目录下。这个目录包含了所有静态文件index.html,assets/*.js,assets/*.css, 图标等。你可以运行npm run preview命令在本地预览构建后的效果确保一切正常。4.4 部署到线上因为这是一个纯静态网站你可以将其部署到任何静态网站托管服务上。以下是几种常见方案方案一GitHub Pages (免费且简单)将你的代码推送到GitHub仓库。在仓库的Settings-Pages页面选择构建源为GitHub Actions如果你配置了构建工作流或直接选择main分支下的/docs文件夹。更常见的做法是配置一个GitHub Actions工作流在每次推送到main分支时自动运行npm run build并将dist目录的内容部署到GitHub Pages。配置完成后你的站点将可以通过https://[你的用户名].github.io/[仓库名]访问。方案二Vercel / Netlify (对前端项目最友好)将代码导入到Vercel或Netlify。这些平台会自动检测到你的项目是Vite项目并提供默认的构建命令npm run build和输出目录dist。点击部署几分钟后就会获得一个永久的、带HTTPS的域名如xxx.vercel.app。优势它们支持自动部署。当你向GitHub仓库推送代码时会自动触发重新构建和部署实现CI/CD。方案三传统Web服务器 (如 Nginx, Apache)将dist目录下的所有文件通过FTP或SCP上传到你的服务器Web根目录例如/var/www/html/navigator。配置你的Web服务器如Nginx将域名指向这个目录。这种方式给你最大的控制权适合已经拥有服务器的用户。注意事项部署后的路径问题如果你部署到非根路径例如https://yourdomain.com/nav/需要配置Vite的base选项。在vite.config.js中设置base: /nav/这样所有资源路径才会正确生成。否则页面可能会找不到JS、CSS和图标文件。5. 高级定制与功能扩展基础功能满足后你可以根据自己的需求对这个导航器进行深度定制。5.1 修改主题与样式所有视觉样式都在src/css/style.css中。你可以轻松地更换主题色、字体、卡片样式等。切换亮色/暗色主题可以通过CSS变量Custom Properties来实现。在:root中定义一套浅色变量在[data-themedark]中定义一套深色变量。然后在页面中添加一个切换按钮通过JavaScript修改document.documentElement的dataset.theme属性来切换。更改布局密度调整.site-grid中的gap和minmax()中的最小宽度值可以控制卡片的间距和每行显示的数量。自定义字体使用import或font-face引入Google Fonts或本地字体然后修改body的font-family。5.2 添加键盘快捷键对于效率工具键盘快捷键是灵魂。我添加了几个快捷键/聚焦到搜索框。Esc清空搜索框。Tab/ShiftTab在分类筛选按钮间导航。Enter打开当前焦点所在或第一个站点卡片。实现方法是为document添加keydown事件监听器根据event.key和event.ctrlKey/metaKey等状态执行相应操作。注意要使用event.preventDefault()来阻止浏览器默认行为比如/键可能会触发浏览器自带的搜索。5.3 集成天气或便签小部件如果你想让它更像一个个人仪表盘可以集成一些简单的微件。天气可以调用免费的天气API如 OpenWeatherMap注意需要注册API Key。在页面加载时用fetch获取你所在城市的天气信息然后渲染到页面一角。切记API Key不能直接暴露在前端代码中否则会被滥用。对于静态页面一个变通方法是使用一个无需认证的、免费的简单API或者将天气功能作为可选插件提示用户自行配置后端代理。便签一个纯前端的便签功能很容易实现。使用localStorage来存储便签内容。在页面上添加一个文本框和一个保存按钮加载时从localStorage读取内容保存时写入。这样数据就保存在你的浏览器本地了。5.4 实现数据备份与同步sites.json是你的核心资产。除了用Git进行版本管理你还可以导出/导入功能在页面中添加两个按钮。“导出”按钮将当前的siteData数组转换为JSON字符串并触发下载。“导入”按钮提供一个文件选择器读取用户上传的JSON文件解析后替换当前的siteData并刷新页面。数据可以保存在localStorage作为缓存但sites.json文件始终是数据源。多端同步如果你部署在云端那么任何能访问该URL的设备都能看到最新的导航页。如果你想在不同设备间同步本地的修改比如在办公室电脑上添加了一个新站点想同步到家里电脑最好的方式还是通过Git。将你的项目文件夹放在像 Dropbox、iCloud Drive 或 Syncthing 这样的同步盘里让sites.json文件自动同步。6. 常见问题与排查实录在开发和使用的过程中我遇到了一些典型问题这里记录下来供你参考。6.1 图标不显示或显示为破碎图片这是最常见的问题。原因一图标路径错误。检查sites.json中的icon字段值是否与src/assets/icons/目录下的文件名完全一致包括大小写。在main.js中检查构建图标URL的逻辑。在Vite中通常使用new URL(./assets/icons/${site.icon}, import.meta.url).href来获取资源URL。原因二图标文件格式问题。确保你使用的是SVG文件。虽然浏览器也支持PNG、ICO但为了统一管理和清晰度建议全部使用SVG。用文本编辑器打开SVG文件检查其内容是否是一个有效的XML格式的SVG。原因三构建后路径问题。开发环境正常但部署后图标不显示。这几乎总是因为部署路径base配置不正确。检查vite.config.js中的base设置并确保服务器正确配置了静态资源目录。排查步骤打开浏览器的开发者工具F12切换到“网络(Network)”标签页。刷新页面查看是否有加载图标文件的请求。如果请求失败状态码为404仔细对比请求的URL和图标文件的实际位置。如果请求成功但图标不显示检查图标文件内容是否有效。6.2 搜索功能不工作或结果不正确现象输入关键词无反应。首先检查搜索输入框的input事件监听器是否成功绑定。在main.js中对应的代码行打上debugger或console.log看事件是否触发。现象搜索结果包含不匹配的项。检查你的搜索过滤逻辑。确保你在比较之前将搜索词和待匹配的文本都转换为小写.toLowerCase()并进行去除空格处理.trim()。检查你是否同时在name、description和tags数组中进行搜索。现象中文搜索不灵敏。原生的includes()对中文是有效的。但如果想支持拼音首字母搜索就需要引入额外的库如pinyin在构建或初始化时为每个站点名称生成拼音字段并加入搜索索引。6.3 部署后页面空白或JS/CSS加载失败检查控制台错误信息。最常见的错误是Failed to load resource: net::ERR_ABORTED 404这表示资源找不到。确认部署目录。确保服务器上存放的是dist目录下的内容而不是dist目录本身。即服务器根目录下应该有index.html和assets文件夹。检查Vite的base配置。如果你部署在子路径如/nav/vite.config.js中的base必须设置为/nav/。同时服务器也需要正确配置将所有请求重定向到index.html对于单页应用的路由和历史模式是必须的但本项目目前是纯静态如果只有首页则不需要。6.4 在移动设备上布局错乱使用浏览器开发者工具的“设备模拟”功能检查在不同屏幕宽度下的样式。核心检查点media媒体查询是否生效.site-grid的grid-template-columns在移动端是否过于拥挤可以尝试在手机端媒体查询中将列数固定为2或3列并增大卡片内边距padding和间隙gap以提升触摸操作的友好性。触摸反馈确保卡片在触摸时有明显的:active状态样式例如改变背景色或缩小一点以提供即时反馈。6.5 性能优化小技巧虽然项目本身已经很轻量但仍有优化空间图标懒加载如果未来图标库变得非常大可以考虑对非首屏的图标使用loadinglazy属性。但对于内嵌的SVG或Base64图标此属性无效。一个更有效的方法是只加载当前显示分类的图标。搜索防抖为搜索输入框的input事件添加防抖debounce函数。这可以避免在用户快速输入时频繁地执行过滤和渲染操作提升性能。可以使用简单的setTimeout实现或者使用lodash.debounce。压缩sites.json在构建时可以使用一个脚本移除JSON中不必要的空格和换行虽然节省的体积很小但是一种好习惯。构建并运行起你自己的site-navigator后最大的体会是“掌控感”的回归。它不再是一个杂乱无章的书签栏而是一个完全按你心意组织的数字工作台。每次打开浏览器看到这个干净、高效、完全属于自己的页面都能立刻进入工作状态。从技术实现上看这个项目也很好地诠释了“简单即美”的理念用最直接的技术解决了真实的需求。如果你也受困于书签管理不妨花上一个小时克隆这个项目按照你的喜好配置一番它很可能成为你日后每天使用频率最高的网页之一。