A lightweight, password-protected note-taking app with real-time cross-device sync, built on EdgeOne Pages (React Router v7 + EdgeOne KV).
- Password protection — Single shared password guards all notes; session persists for 30 days
- Dual storage backends — Switch between EdgeOne KV and EdgeOne Pages Blob from the sidebar toggle; the two datasets are fully independent
- Real-time sync — Notes sync instantly across devices via WebSocket push + HTTP polling fallback
- Auto-save — Changes are saved automatically 800 ms after you stop typing
- Image attachments — Attach images by clicking, dragging, or pasting (supports screenshots)
- Double-click any image to open a full-screen lightbox
- Right-click for: view full size, download, remove
- Resizable sidebar — Drag the sidebar edge to adjust its width
- Dark / Light theme — Persisted in
localStorage - Bilingual UI — Toggle between English and Chinese at any time
- Mobile responsive — Full-screen editor on small screens with a back button
| Layer | Technology |
|---|---|
| Framework | React Router v7 (SSR) |
| Language | TypeScript |
| Styling | Tailwind CSS v4 |
| Icons | Lucide React |
| Build | Vite |
| Storage | EdgeOne KV + EdgeOne Pages Blob (@edgeone/pages-blob) |
| Runtime | EdgeOne Pages Functions (Edge + Node.js) |
| Real-time | WebSocket (node-functions/sync.js) + polling |
# Clone
git clone https://github.com/sxwzxc/syncnote.git
cd syncnote
# Install dependencies
npm install
# Start local dev server (EdgeOne CLI required)
edgeone pages devSet the following environment variables in your EdgeOne Pages project:
| Variable | Description |
|---|---|
PASSWORD |
Login password for the app |
notesKV |
Bound KV namespace for storing notes |
Deploy:
edgeone pages deployLearn more: EdgeOne CLI docs
app/
├── routes/
│ └── home.tsx # Main notes UI (auth + editor + sidebar)
├── components/
│ └── ui/button.tsx # Reusable button component
├── lib/utils.ts # cn() utility
├── root.tsx # Root layout
└── routes.ts # Route definitions
edge-functions/
├── api/
│ ├── auth.js # POST /api/auth — password verification
│ ├── notes.js # GET / POST /api/notes (?storage=kv|blob)
│ ├── notes/[id].js # GET / PUT / DELETE /api/notes/:id (?storage=kv|blob)
│ ├── storage.js # GET /api/storage — backend availability check
│ └── upload-url.js # POST /api/upload-url — presigned Blob upload URL
node-functions/
└── sync.js # WebSocket server for real-time sync
public/ # Static assets
- Dual storage — Every notes endpoint accepts a
?storage=kv|blobquery parameter (defaults tokv). KV reads/writes the boundnotesKVnamespace; Blob uses the@edgeone/pages-blobSDK with a store namednotesand strong consistency. The two backends share no data, so toggling in the UI switches between independent datasets. The frontend persists the active backend inlocalStorage(syncnote_storage). - KV storage — Each note is stored as a JSON value under its UUID key. An index key (
notes_index) holds the list of note IDs and metadata. - Auto-save conflict resolution — If a remote update arrives while the user has pending local changes, the local version wins (the next auto-save will overwrite the remote).
- Note size limit — 25 MB per note (including base64-encoded images). A size indicator is shown in the editor.
MIT License