本文记录我独立完成 SnapPage 这个项目的全过程,包括需求分析、技术选型、踩坑记录和产品思考。项目为非开源的私人作品,仅供学习交流。
一、为什么要做这个工具
平时上网收藏了不少文章和网页,但时间久了问题接踵而至:
收藏的链接变成了 404
网页改版后内容面目全非
想保存的资料,用浏览器
Ctrl + S存下来排版全毁、图片丢失看到有价值的长文,广告和干扰元素太多,阅读体验极差
市面上也不是没有解决方案,但要么是付费的,要么需要注册账号,要么会把你的数据上传到云端。我只是想简单地"把当前这个页面保存下来",为什么要这么复杂?
于是决定自己动手,做一个无需注册、无需登录、用完即走的网页快照工具。
二、功能设计:四个核心场景
在动手写代码之前,先梳理清楚用户真正需要什么。我总结了四类最常见的"保存网页"需求:
场景一:整页截图
很多人需要把网页完整截下来,可能是为了存档,可能是为了分享。系统自带的截图工具只能截一屏,遇到长网页就无能为力了。
所以第一个功能就是全页截图,而且要支持不同设备的视口尺寸:
桌面端(1920x1080)
平板端(768x1024)
手机端(375x812)
截图后还要能直接复制到剪贴板,方便粘贴到微信、文档或者邮件里。
场景二:导出 PDF
截图适合分享,但不适合打印和归档。有些表格、文档类的网页,存成 PDF 才是最合适的。
这里有个细节很容易忽略:不同网页的宽度差异很大。如果用固定 A4 尺寸,很多宽表格会被截断。所以我在生成 PDF 之前,会先测量页面的实际宽高,如果太宽就自动切换横版,甚至使用自定义纸张尺寸,确保内容不被裁剪。
场景三:HTML 离线存档
截图和 PDF 都是"静态"的,如果我想保留网页的原始交互和样式,就需要存成 HTML。
但直接保存原始 HTML 有个问题:里面的 CSS、图片链接都是相对路径或者外网地址,断网后就打不开了。解决方式是在 HTML 头部注入一个<base href>标签,指向原始网址,这样即使离线打开,浏览器也能正确解析资源路径。
场景四:阅读模式提取
现在的网页设计越来越复杂,广告、弹窗、侧边栏、相关推荐……真正想看的正文内容反而被淹没。
我引入了 Mozilla 的 Readability 算法,自动识别文章正文,去掉干扰元素,输出一个清爽的阅读视图。更进一步,提取出的内容可以直接导出为 Markdown,方便导入到 Obsidian、Notion 等笔记软件中。
三、技术选型与架构设计
3.1 为什么用 Playwright + Chromium
网页快照的核心是"在服务端打开一个浏览器,访问目标网址,然后做各种操作"。
我对比了几种方案:
Puppeteer:Google 出品,但社区活跃度近年有所下降
Playwright:Microsoft 出品,支持多浏览器,API 设计更现代,社区更活跃
外部服务(如 Browserless.io):需要付费 Token,不符合"零成本"的初心
最终选择了Playwright + @sparticuz/chromium。Playwright 负责浏览器自动化,@sparticuz/chromium 提供适合 Serverless 环境的 Chromium 二进制包。本地开发时自动检测系统已安装的 Chrome/Edge,部署到线上时自动切换为打包好的 Linux 版本。
3.2 为什么坚持"无状态"设计
这个项目从设计之初就定下了几个"不":
不建数据库— 不需要 SQLite、Postgres,也不需要 ORM
不存文件— 不需要 S3、对象存储,生成的结果直接返回给浏览器
不存用户数据— URL 不记录、结果不缓存、行为不追踪
这样做有几个好处:
架构极简,部署和维护成本极低
隐私安全,用户不用担心自己的浏览记录被保存
符合 Serverless 理念,每个请求独立处理,水平扩展无压力
3.3 部署平台的选择
一开始考虑过 Cloudflare Pages,但很快被否定了。因为 Playwright 需要在 Node.js 环境里运行浏览器二进制文件,而 Cloudflare Workers 运行在 V8 隔离环境中,不支持执行本地二进制文件。
所以最终选择了Vercel。它的 Serverless Function 支持 Node.js 运行时,而且有充足的超时时间配置(Pro 版支持到 60 秒),对于网页渲染和截图来说足够用了。
四、开发过程中踩过的坑
坑一:Chromium 冷启动延迟
Serverless 的特点是"按需启动",函数冷启动时 Chromium 需要从头初始化,第一次请求可能要等 5-10 秒,体验很差。
解决方案是做了一个warmup 机制:页面加载时,前端偷偷发一个/api/warmup请求,让 Serverless Function 提前把浏览器启动起来。等用户真正输入 URL 点击生成时,浏览器已经准备好了,响应速度提升非常明显。
坑二:PDF 尺寸自适应
一开始 PDF 直接用固定 A4 尺寸,结果很多宽表格被截断,用户体验很差。
后来改为先测量后生成的流程:
先用 Playwright 打开页面,获取
scrollWidth和scrollHeight如果宽度超过 A4 的 1.2 倍,自动切换为横版
如果横版还不够,使用自定义纸张尺寸(Chrome 最大支持 200 英寸)
正常页面则保持标准 A4
坑三:部分网站在服务端被阻断
在提取页面元信息(title、description、favicon)时,发现有些网站(比如 Google)在 Node.js 的网络层会直接返回阻断页面,拿不到正确的 meta 标签。
解决方案是做了一个 fallback:先用轻量级的 HTTP 请求尝试获取,如果失败,再用 Playwright 的独立网络栈重新获取。Playwright 的浏览器网络栈和用户真实浏览器的请求特征更接近,大部分阻断都能绕过。
坑四:jsdom 的 ESM 兼容问题
项目早期用 jsdom 做 DOM 解析,但 jsdom v27 开始改为纯 ESM 模块,和 Next.js 的 CJS 构建流程冲突,编译直接报错。
最后换成了linkedom,它更轻量、兼容 CJS,而且性能更好。配合正则表达式做辅助提取,完全替代了 jsdom 的职责。
五、产品层面的思考
5.1 为什么不做用户系统
很多人建议我加上注册登录、历史记录、收藏夹这些功能。但我始终觉得,工具的价值在于解决当下的问题,而不是捆绑用户。
加了用户系统,就要维护数据库、处理密码安全、做权限管理,整个项目的复杂度会指数级上升。更关键的是,用户的使用链路会变长:打开网站 → 注册 → 登录 → 验证邮箱 → 才能使用。很多人在前两步就流失了。
"打开就用"和"注册后用",体验差距是巨大的。
5.2 双语支持的取舍
项目支持中英文双语切换,所有 UI 文案、70 条励志名言、法律免责声明都完整翻译。这个决策源于一个简单的事实:工具类产品的用户可能来自任何地方,语言不应该成为门槛。
但翻译质量也是个大工程,很多技术术语在不同语境下的译法需要反复斟酌。比如 "Snapshot" 到底该叫"快照"还是"截图"?最后根据使用场景做了区分:功能层面叫"快照",图片输出时叫"截图"。
5.3 法律免责声明的必要性
这是一个容易被忽视但非常重要的点。工具本身是中性的,但用户可能用它下载受版权保护的内容。如果不做免责声明,运营者可能承担连带责任。
我在页面底部加了一个免责声明弹窗,明确了几点:
本工具为纯技术服务,不存储任何数据
用户需自行承担使用合规责任
工具运营者不承担任何连带责任
虽然看起来是"自我保护",但对自己和用户都是负责的表现。
六、一些数据与反思
项目从构思到 MVP 完成,断断续续花了大约三周时间。其中写代码只占了一半,另一半时间在纠结产品细节:按钮该放在哪里、进度条用什么颜色、错误提示怎么写才不让用户焦虑。
目前支持的功能已经覆盖了大多数"保存网页"的场景,但还有一些想法没来得及实现:
暗色/亮色主题切换:目前只有深色主题,虽然我自己很喜欢,但确实不是所有人的偏好
本地历史记录:用 localStorage 保存最近使用过的 URL,方便二次访问
批量生成:一次性粘贴多个 URL,逐个生成快照
这些都不涉及架构大改,后续有时间会慢慢加上。
七、写在最后
做这个项目最大的收获不是技术层面的,而是对"工具类产品"的理解:最好的工具应该是隐形的,用户不应该感受到它的存在,只应该感受到问题被解决了。
当你粘贴一个网址、点击按钮、几秒钟后拿到想要的结果,整个过程应该是自然且无痛的。任何多余的步骤、任何需要学习成本的设计,都是对这个理念的背离。
如果你也在做类似的项目,或者有更好的想法,欢迎一起交流。技术的乐趣从来不只是写代码,而是做出有人愿意用的东西。
SnapPage — Paste a URL, get a snapshot
关于项目:SnapPage 为个人独立开发的非开源项目,仅供学习交流使用。文中提到的技术方案和踩坑记录,希望能对正在做类似项目的同学有所帮助。