基于 memos / Obsidian 格式的云端 Memos 网页应用。静态前端可部署到 Cloudflare Pages、Netlify、Vercel;memos 数据存储在 WebDAV 的 basic.memos.md 文件中,打开页面后动态读取并渲染,支持在线编辑。
根目录 basic.memos.md 是格式样例,完整格式说明见下文第 1–3 节。
复制 .env.example 为 .env 并填写:
| 变量 | 必填 | 说明 |
|---|---|---|
SITE_PASSWORD |
是 | 网站访问密码 |
WEBDAV_URL |
是 | WebDAV 根地址,如 https://example.com/remote.php/dav/files/user |
WEBDAV_USERNAME |
是 | WebDAV 用户名 |
WEBDAV_PASSWORD |
是 | WebDAV 密码 |
WEBDAV_FILE_PATH |
否 | 文件路径(相对 WEBDAV_URL),默认 basic.memos.md |
AUTH_SECRET |
强烈建议 | 会话签名密钥,至少 32 位随机字符串 |
ALLOWED_ORIGIN |
否 | 跨域前端地址(逗号分隔),同域部署无需设置 |
需要两个终端(或 API 已在 8788 端口运行时只需启动前端):
npm install
# 终端 1:API(读取 .env)
npm run dev:api
# 终端 2:前端(/api 代理到 8788)
npm run dev浏览器打开 **http://localhost:5173**,输入 .env 中的 SITE_PASSWORD 登录。
| 服务 | 地址 | 说明 |
|---|---|---|
| 前端 | http://localhost:5173 | Vite 开发服务器 |
| API | http://localhost:8788 | 本地 API,读取 .env |
验证 API 是否正常:
curl http://localhost:8788/api/auth/check
# 应返回 {"authenticated":false}本项目是 静态前端 + Serverless API 架构:浏览器访问 SPA,API 在服务端读取环境变量并连接 WebDAV,密钥不会打包进前端。
-
准备 WebDAV 存储
- 支持 Nextcloud、坚果云、Synology、Seafile 等 WebDAV 服务
- 在 WebDAV 根目录创建或确认存在
basic.memos.md(也可通过WEBDAV_FILE_PATH指定其他路径) - 参考根目录
basic.memos.md了解文件格式
-
复制环境变量模板
cp .env.example .env
-
填写并检查变量
变量 必填 说明 SITE_PASSWORD是 网站访问密码,建议 16 位以上随机字符 WEBDAV_URL是 WebDAV 根地址,如 https://example.com/remote.php/dav/files/userWEBDAV_USERNAME是 WebDAV 用户名 WEBDAV_PASSWORD是 WebDAV 密码或应用专用密码 WEBDAV_FILE_PATH否 相对路径,默认 basic.memos.mdAUTH_SECRET强烈建议 会话签名密钥,至少 32 位随机字符串,勿与 SITE_PASSWORD 相同 ALLOWED_ORIGIN否 跨域前端地址(逗号分隔),同域部署无需设置 -
本地验证
npm run dev:api # 终端 1 npm run dev # 终端 2
登录后确认能加载、编辑、保存 memos。
-
构建检查
npm run build
无报错后再部署。
- 将项目推送到 GitHub / GitLab / Bitbucket
- 打开 Vercel → Add New Project → 导入仓库
- 框架预设选 Vite,保持默认:
- Build Command:
npm run build - Output Directory:
dist
- Build Command:
- 展开 Environment Variables,添加全部必填变量(Production / Preview / Development 按需勾选)
- 点击 Deploy
- 部署完成后访问
https://你的项目.vercel.app
说明:
api/目录会自动作为 Serverless Functions 处理/api/*请求vercel.json已配置 SPA 回退到index.html- 生产环境务必启用 HTTPS(Vercel 默认提供)
- 打开 Netlify → Add new site → Import an existing project
- 连接 Git 仓库
- 构建设置(
netlify.toml已预配置,一般无需修改):- Build command:
npm run build - Publish directory:
dist
- Build command:
- Site configuration → Environment variables 中添加全部环境变量
- 触发部署
说明:
- API 由
netlify/functions/api.ts提供 /api/*通过netlify.toml重写到 Netlify Functions
- 打开 Cloudflare Dashboard → Workers & Pages → Create
- 选择 Pages → Connect to Git
- 构建设置:
- Framework preset: None 或 Vite
- Build command:
npm run build - Build output directory:
dist
- Settings → Environment variables 中添加变量(敏感项可设为 Encrypt)
- 保存并部署
说明:
- API 由
functions/[[path]].ts(Pages Functions)提供 wrangler.toml中pages_build_output_dir = "dist"已配置
按顺序检查:
# 1. 认证检查(未登录应为 false)
curl https://你的域名/api/auth/check
# 2. 浏览器打开站点,输入 SITE_PASSWORD 登录
# 3. 创建一条 memo 并刷新,确认数据已写入 WebDAV常见问题:
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 登录后一直加载失败 | WebDAV 地址或凭据错误 | 检查 WEBDAV_* 变量,确认 WebDAV 可从公网访问 |
| 401 Unauthorized | 密码错误或 Cookie 未生效 | 确认 SITE_PASSWORD;生产环境需 HTTPS |
| 404 on /api/* | 平台函数未正确配置 | 确认使用了对应目录(api/ / netlify/functions/ / functions/) |
| 保存失败 500 | WebDAV 无写权限或路径错误 | 检查 WEBDAV_FILE_PATH,确认账号有 PUT 权限 |
| 登录频繁被限 | 触发速率限制 | 15 分钟内最多 10 次失败尝试,稍后再试 |
- 不要将
.env提交到 Git 或上传到公开位置 - 生产环境必须设置独立的
AUTH_SECRET - WebDAV 账号建议使用最小权限(仅读写 memos 文件)
- 坚果云等建议使用应用密码,而非主账号密码
- 部署平台的环境变量界面填写密钥,不要写进代码仓库
- 密码保护访问(环境变量
SITE_PASSWORD) - 从 WebDAV 动态加载
basic.memos.md - 支持 Thino 格式:日记(JOURNAL)、待办(TASK-TODO)、已完成(TASK-DONE)
- Markdown 渲染、标签筛选、搜索、置顶
- 快速记录、编辑、删除、待办勾选
- 修改后写回 WebDAV
memos-web/
├── src/ # React 前端
├── server/ # 共享 API 逻辑(认证、WebDAV、Thino 解析)
├── api/ # Vercel Serverless
├── netlify/functions/ # Netlify Functions
├── functions/ # Cloudflare Pages Functions
├── basic.memos.md # 格式样例
└── README.md # 本文档 + Thino 格式说明
基于 Flutter 的 Thino / Memos 客户端格式规范。根目录 basic.memos.md 涵盖所有记录类型与写法。
Thino 文件按日期块组织,每个日期下有多条 thino 记录。
# YYYY-MM-DD ← 日期标题(一级标题)
> [!thino] YYYY/MM/DD HH:mm:ss %% [id::...] [thinoType::...] [pinned::true] %%
> 记录正文第一行
> 记录正文第二行
>
> [!thino] YYYY/MM/DD HH:mm:ss %% [id::...] [thinoType::JOURNAL] %%
> ...
| 格式 | 含义 |
|---|---|
# 2026-06-10 |
当天所有记录的容器。解析器用正则 ^#\s+(\d{4}-\d{2}-\d{2})\s*$ 识别。 |
| 字段 | 格式 | 含义 |
|---|---|---|
| 引用块类型 | [!thino] |
固定,表示这是一条 Thino 记录 |
| 时间 | 2026/06/10 18:00:01 |
记录创建/显示时间,YYYY/MM/DD HH:mm:ss |
| 元数据区 | %% ... %% |
中间放结构化字段 |
| id | [id::a100000000000001] |
唯一 ID,同步、编辑、删除都依赖它,不能丢 |
| 类型 | [thinoType::JOURNAL] |
记录类型,见下文 |
| 置顶 | [pinned::true] |
可选,置顶标记 |
- 每条记录头下面的连续
>行都属于该记录正文。 - 写入时每行变为
> 正文内容;空行写为单独的>。 - 应用内正文是去掉
>前缀后的内容,不含记录头。 - 不单独解析标题字段:若用户写了
### 标题,它保留在正文中,由 Markdown 渲染。
| thinoType | 含义 | 正文格式 | UI 表现 |
|---|---|---|---|
JOURNAL |
普通日记 | 任意 Markdown 正文 | 卡片渲染 Markdown;底部显示标签 |
TASK-TODO |
进行中待办 | - [ ] / - [x] 清单行 + 可选标签行 |
卡片显示可点击 checkbox 列表 |
TASK-DONE |
已完成待办 | 纯文本行(每项一行)+ 可选标签行 | 卡片显示全部已勾选的 checkbox 列表 |
- 支持完整 Markdown:标题、粗体、斜体、删除线、列表、引用、链接、代码块等。
- Markdown 里的
- [ ]/- [x]只是展示,不会触发 TASK-TODO 逻辑。
存储格式:
> [!thino] ... [thinoType::TASK-TODO] %%
> - [ ] 待办第一项
> - [x] 待办第二项(已勾选)
> - [ ] 待办第三项
> #工作
> #紧急规则:
- 清单行:必须以
- [ ]或- [x]开头。 - 标签行:以
#标签名单独成行,一行一个标签。 - 全部勾选:自动变为
TASK-DONE。 - 恢复待办:
revertDoneToTodo恢复为 TASK-TODO,各项保持- [x]。
存储格式:
> [!thino] ... [thinoType::TASK-DONE] %%
> 已完成待办第一项
> 已完成待办第二项
> #复盘
> #归档- 正文每项一行纯文本,没有
- [ ]前缀。 - 点击某一项取消完成:仅该项变
- [ ],其余仍- [x]。
| 规则 | 说明 |
|---|---|
| 格式 | #标签名,# 后不能有空格 |
| 合法字符 | 字母、数字、_、-、中文 |
| 存储格式 | 一行一个标签,在正文末尾 |
文件示例:
> 正文内容
> #病情稳定
> #测试- 待办 + 标签:标签必须单独成行。
- 已完成待办恢复:用
revertDoneToTodo,不要直接用 unchecked 转换。 - JOURNAL 与 TASK:仅
thinoType决定待办逻辑;正文里的 markdown checkbox 不触发 TASK 状态机。 - 标签存储:输出一行一个
#标签,不是#a #b同行。


