跨设备传文件总是很烦?我用 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限制只有你自己能用,不怕被陌生人塞东西
三、为什么我没有「做一个 App」
最早我也想过做 App,甚至画过原型图。但仔细想想,这个需求的本质是「跨平台、低频但即时」,App 反而是最差的形态:
- 用户要装两端
- 要解决推送、后台保活、各种系统的权限弹窗
- 还要面对 App Store 审核
而 Web 是天然跨平台、零安装、URL 即入口 的。再叠加上 Vercel 的免费额度足够个人使用、Serverless + 客户端直传 Blob 可以把基础设施成本压到几乎为零,这件事就变成了一个周末可以收工的小项目。
四、技术选型
| 层 | 选型 | 选择理由 |
|---|---|---|
| 框架 | Next.js 14 (App Router) | 前后端一把梭,API Routes 写后端零心智负担 |
| 元数据 | Upstash Redis | Serverless 友好的 REST API,免费额度够个人玩 |
| 媒体存储 | Vercel Blob | 客户端直传,绕过 Functions 4.5 MB 请求体上限 |
| 部署 | Vercel Fluid Compute | Node.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 即可。
六、一键部署
整个项目支持点一个按钮就完成部署:
- 点 README 里的 Deploy with Vercel 按钮
- 在 Vercel 部署流程里勾选 Upstash Redis 和 Vercel Blob 这两个 Marketplace 集成(自动注入对应环境变量)
- 在 Settings → Environment Variables 加两个:
ALLOWED_PHONE:你的手机号(即登录账号)CRON_SECRET:保护 cron 端点的随机串
- 重新部署一次让环境变量生效
整个过程不超过 5 分钟,全程零代码。部署完直接得到一个 xxx.vercel.app 的域名,绑你自己的域名也只需要在 Vercel 后台点几下。
七、实际使用一周后的感受
部署上线后我用了一周多,主要场景:
- 写技术文章:电脑上写到一半,去阳台用 iPad 继续看一眼资料,把链接顺手粘到剪贴板,回到电脑直接粘进来
- 传截图:在手机上拍的图随手丢上去,电脑端处理完顺便清掉
- 传命令行:在 Mac 上写好的服务器命令,打开手机粘到 SSH 工具里,再也不用扫二维码登微信
最大的感受是 「打开网页」这个动作真的零摩擦。比打开微信选联系人快太多了。
八、写在最后
这个项目的代码量不大(核心逻辑大概 800 行),但解决了我每天都要遇到几次的小痛点。源代码完全开源,欢迎 Fork 和魔改:
- GitHub:github.com/smilezyl202…
- License:MIT
如果觉得有用,给个 Star 是对我最大的鼓励 😉。
如果你有改进想法或新需求,也欢迎在 issues 里讨论。可能会做的 roadmap:
- 端到端加密
- PWA + 离线缓存
- WebRTC P2P 模式(适合大文件 / 局域网内传输)
- 浏览器扩展,全局快捷键直接同步剪贴板
一个小小的工具,胜过十个完美的计划。 如果你也常被「跨设备传东西」这件事烦到,欢迎试试看。