本文记录一个第三方 npm 下载量可视化工具的诞生过程与技术选型。
一、为什么要自己写?
起因很简单:想查自己发的 npm 包下载量。
官方提供的下载统计能力偏「单点」——适合看某个包、某一段时间的基础数字,但要做多包对比、看多年趋势,或者换一种更直观的曲线图体验,就会觉得不够用。
社区里大家熟知的第三方站点 npm-stat 曾经很好用,但后来服务不稳定、经常打不开。工具链一断,日常想看一眼下载曲线就变成「打开一堆文档、自己拼 API」。
于是干脆自己写一个:
- 技术栈选自己熟悉的 Nuxt 3 + Vue 3 + TypeScript
- 部署到 Netlify,免运维、带 HTTPS
- 功能上补齐官方与老站缺口:中英切换、深色模式、多包对比、跨年长区间查询
项目名仍叫 npm-stat,定位很明确:个人可用的第三方统计小工具,不是官方替代品。
二、做成了什么?

当前能力一览:
| 能力 | 说明 |
|---|---|
| 多包查询与对比 | 英文逗号分隔,如 vue, react, @babel/core,折线图多色叠加 |
| 跨年长区间 | 自定义起止日期,最长约 近 10 年;服务端自动分段请求再合并 |
| 快捷时间 | 近 7 / 30 / 90 / 365 天(结束日取「昨天」,避开当日未汇总的 0) |
| 中英文切换 | 界面文案、页脚免责声明随语言切换,默认英文 |
| 深色模式 | 右上角一键切换,偏好写入 localStorage |
| 统计卡片 | 周期总下载、日均、单日最高 / 最低 |
| SEO / 部署 | SSR + @nuxtjs/seo,Netlify 构建发布 |
数据流很直接:浏览器 → 本站 Nitro API → npm 官方下载接口,不落库,实时(带短时内存缓存)。
三、技术栈与目录
Nuxt 3(SSR)
├── Vue 3 + TypeScript # 前端
├── ECharts(按需引入) # 折线趋势图
├── Tailwind CSS # 样式
├── Nitro server/api # 代理 npm、分段拉取、缓存
├── @nuxtjs/seo # sitemap / robots / canonical
└── Netlify(nitro preset) # HTTPS + Serverless
核心目录大致如下:
npm-stat/
├── pages/index.vue # 首页(查询 + 图表)
├── components/ # 表单、图表、统计卡、主题/语言切换、页脚
├── composables/ # 日期、查询状态、主题、i18n
├── server/api/npm/download.get.ts # 下载量代理 + 分段合并 + 缓存
├── locales/messages.ts # 中英文案
├── utils/date.ts # 日期规范化、分段切分
├── nuxt.config.ts
├── netlify.toml
└── docs/ # 技术分享(本文)
四、几个值得讲的技术点
1. 为什么必须做服务端代理?
浏览器直连 https://api.npmjs.org/... 会遇到 CORS。把请求收到 server/api,由 Nitro 转发,既能跨域,又能统一错误码、加缓存、做分段逻辑。
官方接口形态:
GET https://api.npmjs.org/downloads/range/:start::end/:package
2. 跨年查询:官方「一次拉太长会被截断」
踩过一个坑:把 2021-01-01 到「现在」塞进一次 range 请求,npm 往往只返回大约近 18 个月的数据,起点会被悄悄改掉。
但按年(或按 ≤365 天一段)去请求,历史年份是有数据的。因此服务端做了:
splitDateRangeChunks(start, end, 365)切段- 逐段请求官方接口
- 按
day合并去重排序后返回前端
这样「查 21 年到现在」才能真正画出完整曲线。区间上限约 10 年,避免 Netlify Function 被拖到超时。
3. 为什么默认查不到「今天」?
即便官方返回了「今天」「昨天」的点,近期经常是 downloads: 0——日维度汇总有延迟。产品上直接把可选结束日上限定为昨天,快捷「近 N 天」也以昨天为终点,减少「图上突然掉零」的误解。
4. 中英文:别和 @nuxtjs/seo 抢 useI18n 这个名字
接入 @nuxtjs/seo 后,线上曾出现标题、表单标签全空、只剩色块的情况。根因是 nuxt-seo-utils 注入了同名 useI18n polyfill,把项目自己的 composable 盖掉了;polyfill 的 t() 签名不同,单参数调用会得到空串。
解决办法:业务 i18n 改名为 useAppI18n,与 SEO 工具链彻底避开命名冲突。
5. 深色模式与 SSR
html.darkclass 策略 + TailwinddarkMode: 'class'app.html内联脚本读localStorage,减轻首屏闪白- 图表坐标轴颜色跟随
isDark重绘
页面默认英文 SSR,有利于 Google 抓取固定文案;语言切换在客户端进行。
6. Netlify 上的超时与缓存
Serverless 默认超时偏紧。对策包括:
- 上游请求超时设在约 8s 内
/api/npm/download5 分钟进程内内存缓存(温实例可复用)- 分段多时包级串行,降低并发打满超时的概率
部署要点:nuxt build,发布目录 dist,NUXT_SITE_URL 指向规范域名。
7. 合规与免责
页脚提供中英文免责声明(随语言切换),强调:
- 第三方独立工具,非 npm 官方站
- 与 npm、GitHub 无合作 / 隶属关系
- 数据来自公共 API,仅供参考,不保证准确
站点内不使用 npm 官方 Logo,文案避免「官方」「正版合作」等误导表述。
五、本地跑起来
npm install
cp .env.example .env # 按需设置 NUXT_SITE_URL
npm run dev
浏览器打开本地地址即可查询。生产构建:
npm run build
npm run preview
六、回顾与还可以做什么
已经解决的问题:
- 官方能力偏单一 → 多包对比 + 曲线 + 统计卡
- 第三方老站不可用 → 自建可控的小工具
- 长区间被截断 → 分段拉取合并
- SEO / 部署 → SSR + Netlify + 基础 SEO 模块
若继续迭代,可以考虑:
- 包名搜索建议(registry 补全)
- 导出 CSV / 图片
- 预渲染若干「热门包对比」内容页,加强 SEO
- 更细的缓存层(KV / Edge)以适配更多并发
七、小结
这个项目不是从「要做一个大而全的平台」开始的,而是从查自己的下载量不方便开始的。官方够基础、第三方挂了,就用 Nuxt 3 把代理、图表、体验和部署串成一条最小可用链路。
如果你也有类似「小工具挂了、官方又不够用」的场景,与其干等,不如花一个周末把核心链路自己搭起来——往往比想象中更可控。

