本项目旨在创建一个功能类似于 Joplin 或 Simplenote 的跨平台离线优先云同步的笔记应用。

技术栈核心:

  • 前端框架: Next.js (使用 App Router)
  • 桌面应用: Electron
  • 后端 & 部署: Vercel
  • 数据库: Vercel Postgres (云端) & Dexie.js (本地)
  • 对象存储: Vercel Blob (用于图片附件)
  • 样式: Tailwind CSS

第一章:项目创建与基础设置

1. 创建 Next.js 项目

首先,我们使用 create-next-app 初始化项目。

npx create-next-app@latest my-cloud-note

在交互式提示中,请确保选择 TypeScriptTailwind CSS

2. 目录结构概览

创建完成后,我们整理并规划了项目的核心目录结构:

  • F:. (项目根目录)
    • package.json: 项目的心脏。定义了项目名称版本依赖脚本等。
    • next.config.js: Next.js 的配置文件。
    • tsconfig.json: TypeScript 的配置文件,定义了编译规则。
    • prisma/schema.prisma: [核心] Prisma 的数据模型文件,定义了云端数据库的表结构,是所有数据结构的“单一事实来源”。
    • .env.development.local: [核心] 本地开发环境变量。next devvercel dev 会自动读取此文件,用于存放数据库连接字符串和各种 API 密钥。极其重要,切勿提交到 Git。
    • electron/:
      • main.js: Electron 的主进程入口。负责创建和管理桌面窗口,并加载我们的 Next.js 应用。
    • public/: 存放静态资源,如 manifest.json (用于 PWA) 和图标。
    • src/: 应用源码核心。
      • app/: Next.js App Router 的根目录。
        • layout.tsx: 全局根布局。
        • page.tsx: 应用的首页。
        • api/: 后端 API 路由。所有与云端交互的逻辑都在这里,它们被部署为 Vercel Serverless Functions
          • sync/route.ts: 负责笔记数据的云同步。
          • upload/route.ts: 负责将图片上传到 Vercel Blob。
        • notebook/[notebookId]/: 动态路由,构成了应用的核心界面。
      • components/: React 组件库。存放可复用的 UI 组件,如 Sidebar.tsx NoteList.tsx SyncButton.tsx 等。
      • lib/: 核心逻辑库。
        • db.ts: 本地数据库定义。使用 Dexie.js 定义了 notebooksnotes 表的结构。
        • sync.ts: 前端同步逻辑。封装了调用 /api/syncfetch 请求,实现了本地与云端的同步。

第二章:核心功能实现与问题排查

1. 本地数据库 (离线优先)

我们使用 Dexie.js 作为浏览器端的 IndexedDB 封装库,在 src/lib/db.ts 中定义了数据表,并明确了主键为 string 类型 (Table<Notebook string>),以确保与 TypeScript 和后续的同步逻辑完美兼容,实现了应用的离线存储能力。

2. 云端数据库 (数据同步)

我们在 Vercel 上创建了 Postgres 数据库 (由 Neon 提供技术支持),并通过 prisma/schema.prisma 文件定义了与本地 DexieDB 对应的 NotebookNote 模型。在 src/app/api/sync/route.ts 中,我们编写了接收本地数据通过 Prisma upsert 到 Postgres并返回云端最新数据的完整同步逻辑。

遇到的问题:

  • 构建时无法连接数据库: Vercel 的构建环境无法访问数据库,导致在 "collecting page data" 阶段 new PrismaClient() 初始化失败。

    • 最终解决方案: 将 new PrismaClient() 的实例化操作,从 API 文件的顶层移动到 POST 等请求处理函数内部。这确保了只在处理真实请求时才尝试连接数据库,完美地解决了构建时错误。
  • Prisma 引擎与 Edge 运行时冲突: 当尝试将 API 部署到 Vercel Edge Functions (runtime = 'edge') 时,频繁遇到 Invalid client engine type 错误。

    • 最终解决方案: 为了追求稳定性和最佳兼容性,我们将所有 API 路由的运行时统一设置为 runtime = "nodejs",并移除了 prisma/schema.prisma 中的 engineType = "wasm" 配置。这使得 API 运行在一个标准的 Node.js 环境中,Prisma 的 binary 引擎可以完美工作。
  • 多窗口数据不同步: 在一个浏览器窗口修改或删除数据后,在另一个新窗口中无法看到最新的更改。

    • 诊断: 确认是 Vercel 的数据缓存 (Data Cache) 导致 /api/sync 返回了旧的数据。
    • 最终解决方案: 在 /api/sync/route.ts 文件顶部添加 export const revalidate = 0。这个指令强力禁用了该路由的任何数据缓存,确保每次请求都命中数据库,返回最新鲜的数据。

3. 图片上传 (集成 Vercel Blob)

我们在 src/app/api/upload/route.ts 中使用 @vercel/blob SDK 实现了图片上传功能,并在前端编辑器组件中集成了粘贴拖拽和点击上传。

遇到的问题:

  • 环境变量问题: 本地能上传,线上 Vercel 不行。报错 Vercel Blob: No token found

    • 解决方案: 认识到环境变量需要通过 Vercel 管理。登录 Vercel 项目的 Storage 标签页,创建并连接 Blob 存储。Vercel 随即自动生成并注入BLOB_READ_WRITE_TOKEN 环境变量。最后,通过 npx vercel env pull 将此 token 同步到本地 .env.development.local 文件,解决了本地和云端的所有认证问题。
  • 前后端 body 格式不匹配: 前端直接发送 File 对象,而后端尝试用 request.formData() 解析,导致 Could not parse content as FormData 错误。

    • 最终解决方案: 统一了前后端的行为。前端使用 new FormData().append('file' file) 来包装文件后端则保持 await request.formData() 来解析,这是最标准兼容性最好的文件上传方式。

第三章:桌面端集成 (Electron)

为了实现跨平台,我们将 Next.js 应用打包进 Electron。

  • 安装 Electron 相关依赖:
    npm install electron electron-builder electron-is-dev concurrently wait-on --save-dev
    
  • 编写主进程 electron/main.js: 这个文件负责创建浏览器窗口。在开发模式下,它加载 http://localhost:3000在生产模式下,我们实现了一个智能的内置静态服务器,它能够正确处理 Next.js App Router 的动态路由和静态资源,通过 index.html 回退机制确保了客户端路由的正常工作。
  • 配置 package.json 脚本: 我们添加了 electron:develectron:build 脚本。electron:build 会先执行 vercel build --prod 来生成一个生产环境的 .vercel/output 目录,然后再由 electron-builder 进行打包。

第四章:部署与发布

1. Web 端部署

Web 端的部署与 Vercel 的 Git 工作流深度集成。

  • 本地构建与预览:

    # 在本地模拟 Vercel 的生产构建
    npx vercel build --prod
    
    # 在本地启动一个完全模拟 Vercel 线上环境的服务器
    npx vercel dev
    

    这个流程是部署前进行最终测试的利器,我们正是用它发现了所有本地 next build 无法暴露的环境差异问题。

  • 发布: 将代码推送到已连接到 Vercel 项目的 Git 仓库主分支,即可自动触发构建和部署。

2. 桌面端发布

我们使用 Git Tag 来管理和触发桌面端的发布流程。

  • 打标签:
    git tag -a v1.0.0 -m "Release version 1.0.0"
    
  • 推送标签:
    git push origin v1.0.0
    
    这可以触发 GitHub Actions (如果配置了) 运行 npm run electron:build,生成各平台的安装包并上传到 Releases 页面。

MyCloudNote AppImage 解压并运行步骤

  1. 构建 Electron 应用: npm run electron:build
  2. 解压 AppImage 文件: ./MyCloudNote-1.0.0.AppImage --appimage-extract
  3. 进入解压目录: cd squashfs-root/
  4. 以无沙箱模式运行应用: ./my-cloud-note --no-sandbox

小贴士:

  • 若 AppImage 无执行权限,先运行 chmod +x MyCloudNote-1.0.0.AppImage
  • 使用 npm install -g asar 后,可以用 asar list app.asar 查看打包内容。

结语

通过这个项目,我们不仅构建了一个实用的应用程序,更重要的是,我们亲身经历并解决了一系列在现代 Web 和桌面开发中极具代表性的问题。从前端的状态同步到后端的环境配置,从本地调试到云端部署,每一步都充满了挑战与收获。**最终我们确定了以 Vercel Serverless Functions (nodejs 运行时) 作为后端,配合“全量覆盖”同步策略和 Dexie.js 本地数据库,构建了一套简单可靠且高效的系统。