跨设备传文件总是很烦?我用 Next.js + Vercel 写了个自部署的「云剪贴板」

0 阅读6分钟

跨设备传文件总是很烦?我用 Next.js + Vercel 写了个自部署的「云剪贴板」

一个周末的小项目,但是真正解决了我每天遇到 N 次的小痛点。 项目地址:github.com/smilezyl202…

一、起源:一个被忽视的高频痛点

不知道你有没有遇到过这样的场景:

  • 在电脑上看到一段命令行,想发到手机里方便拍照对照?
  • 在手机上拍了张白板照片,想立刻丢到电脑上整理到文档里?
  • 在公司电脑写了一半的文字,想到家里另一台电脑继续?

我们的「常规解法」无非这几种,但每一种都有让人不爽的地方:

方案痛点
微信「文件传输助手」必须两端都登录、图片会被压缩、占用聊天记录、隐私可疑
QQ / 钉钉 / 飞书同上,并且要打开庞大的客户端
邮件附件操作步骤多,正式得离谱,传个验证码都觉得过分
AirDrop只在苹果生态内有用,跨 Windows / Android 直接歇菜
U 盘 / 数据线……都 2026 年了

我想要的其实非常朴素:打开浏览器 → 粘贴 → 在另一台设备打开同一个网页 → 看到内容

仅此而已。不要装客户端、不要登录第三方、不要把我的内容喂给某个大公司做训练。

于是有了这个项目:Clipboard Sync —— 一个可以一键自部署到 Vercel 的「云剪贴板」

二、它能做什么

一句话描述:任何设备的浏览器打开同一个 URL,就是同一块剪贴板

具体支持:

  • 文本同步:粘贴一段文字,7 天内任何设备都能拿到
  • 图片传输:单张最大 10 MB,列表自动生成缩略图,3 小时自动清理
  • 任意文件:单个最大 50 MB,直接下载,3 小时自动清理
  • 多入口上传:点附件按钮、或在输入框直接 Ctrl/Cmd+V 把截图/文件粘进去
  • 响应式 UI:桌面端、iOS、Android 浏览器一致体验
  • 单用户白名单:通过环境变量 ALLOWED_PHONE 限制只有你自己能用,不怕被陌生人塞东西

demo.gif

三、为什么我没有「做一个 App」

最早我也想过做 App,甚至画过原型图。但仔细想想,这个需求的本质是「跨平台、低频但即时」,App 反而是最差的形态:

  • 用户要装两端
  • 要解决推送、后台保活、各种系统的权限弹窗
  • 还要面对 App Store 审核

Web 是天然跨平台、零安装、URL 即入口 的。再叠加上 Vercel 的免费额度足够个人使用Serverless + 客户端直传 Blob 可以把基础设施成本压到几乎为零,这件事就变成了一个周末可以收工的小项目。

四、技术选型

选型选择理由
框架Next.js 14 (App Router)前后端一把梭,API Routes 写后端零心智负担
元数据Upstash RedisServerless 友好的 REST API,免费额度够个人玩
媒体存储Vercel Blob客户端直传,绕过 Functions 4.5 MB 请求体上限
部署Vercel Fluid ComputeNode.js 24 LTS,冷启动比传统 Serverless 友好
定时任务Vercel Cron每天兜底清理一次过期媒体

整套技术栈选下来,Hobby 套餐(免费)足以覆盖一个人的日常使用

五、几个值得聊一下的设计点

1. 数据模型刻意做得很「轻」

考虑到这是个单用户场景,没必要上关系型数据库。直接一个 Redis Key 存所有数据:

key:   user:{phone}
value: { records: Record[], lastModified: number }

Record 支持 text | image | file 三种类型,对应不同的渲染逻辑。整条 Key 设置 7 天 TTL,每次访问刷新过期时间——人不再用,数据自动消失,连主动清理都不用写。

2. 大文件不能走 Function

Vercel Function 默认请求体上限 4.5 MB。直接 multipart/form-data 上传一张高清截图就炸了。

正确做法是 客户端直传 Blob

// 前端
const blob = await upload(file.name, file, {
  access: 'public',
  handleUploadUrl: '/api/records/upload',
});

// 后端只签发临时 token
import { handleUpload } from '@vercel/blob/client';
return handleUpload({ request, body, onBeforeGenerateToken, onUploadCompleted });

文件根本不经过 Function,直接走 Vercel Blob 的边缘节点。Function 只负责签发上传令牌、以及在回调里把元数据写入 Redis。省钱、省时、省冷启动

3. 媒体的「双保险」清理

媒体文件需要按 TTL(默认 3 小时)过期,但 Cron 在 Hobby 套餐下每天只能跑一次,时效性不够。所以做了双保险:

  • 懒过滤:每次 getRecords 时遍历列表,把 expiresAt 已到期的过滤掉,并异步 del() 对应 Blob
  • Cron 兜底:每天定时跑一次 /api/cron/cleanup-blobs,处理那些一直没人访问到的孤儿文件

实际跑下来,主要清理工作都被懒过滤承担了,Cron 几乎只是个保险。

4. 不做轮询,省 Redis 额度

Upstash 免费额度按请求数计费。如果前端每隔几秒拉一次,一天就能轻松刷掉几千次请求。

所以前端只在两个时机拉取:

  • 页面加载
  • visibilitychange 切回标签页

人不看页面就不发请求,免费额度可以撑很久。

5. 单用户白名单:最简单的「鉴权」

我没接 OAuth、没做注册系统,就一个环境变量:

// src/lib/auth.ts
const ALLOWED_PHONE = process.env.ALLOWED_PHONE;
export const isAllowedPhone = (phone: string) => phone === ALLOWED_PHONE;

登录页输入手机号,匹配则放行。对于「只给自己用」的工具来说,这就是 100 分的方案——简单、零依赖、没有数据泄露面。

如果你想让朋友也能用,把这里换成 Redis 里的白名单 Set 即可。

六、一键部署

整个项目支持点一个按钮就完成部署:

  1. 点 README 里的 Deploy with Vercel 按钮
  2. 在 Vercel 部署流程里勾选 Upstash RedisVercel Blob 这两个 Marketplace 集成(自动注入对应环境变量)
  3. 在 Settings → Environment Variables 加两个:
    • ALLOWED_PHONE:你的手机号(即登录账号)
    • CRON_SECRET:保护 cron 端点的随机串
  4. 重新部署一次让环境变量生效

整个过程不超过 5 分钟,全程零代码。部署完直接得到一个 xxx.vercel.app 的域名,绑你自己的域名也只需要在 Vercel 后台点几下。

七、实际使用一周后的感受

部署上线后我用了一周多,主要场景:

  • 写技术文章:电脑上写到一半,去阳台用 iPad 继续看一眼资料,把链接顺手粘到剪贴板,回到电脑直接粘进来
  • 传截图:在手机上拍的图随手丢上去,电脑端处理完顺便清掉
  • 传命令行:在 Mac 上写好的服务器命令,打开手机粘到 SSH 工具里,再也不用扫二维码登微信

最大的感受是 「打开网页」这个动作真的零摩擦。比打开微信选联系人快太多了。

八、写在最后

这个项目的代码量不大(核心逻辑大概 800 行),但解决了我每天都要遇到几次的小痛点。源代码完全开源,欢迎 Fork 和魔改:

如果觉得有用,给个 Star 是对我最大的鼓励 😉。

如果你有改进想法或新需求,也欢迎在 issues 里讨论。可能会做的 roadmap:

  • 端到端加密
  • PWA + 离线缓存
  • WebRTC P2P 模式(适合大文件 / 局域网内传输)
  • 浏览器扩展,全局快捷键直接同步剪贴板

一个小小的工具,胜过十个完美的计划。 如果你也常被「跨设备传东西」这件事烦到,欢迎试试看。