This is a hobby project.
I wanted a personal note-taking app with a modern design and fast performance. I could have created a full multi-user system, but it is intended strictly for personal single-person use.
On the landing page, it asks for a login email/password. The email you see there is set via an environment variable at deploy time and baked into the public bundle — it is UX sugar only (it pre-fills the form). Real access control lives in firestore.rules, which scopes every read/write to exactly that one email address, so the corresponding account must exist in Firebase (created manually in the console; there is no self-signup path).
You can create quick notes or write full markdown notes with math rendering (
(Note: screenshots predate the v0.8 redesign — the app now uses a dark theme with subtle glass accents, so these may look a bit different!)
- Build Tool: Vite (Migrated from CRA for extremely fast HMR and builds)
- Authentication: Firebase Authentication Email/Password
- Database: Cloud Firestore (with IndexedDB offline persistence)
- Hosting: Cloudflare Workers (Static Assets)
- UI Components: Ant Design (antd)
- Math Rendering: KaTeX via remark-math/rehype-katex
- Testing: Vitest + Testing Library
- Enable Email/Password Auth from Firebase.
- Add your user to Firebase Authentication — manually from the console, since there is no self-signup path.
- For local development, add your email to the
.envfile:
VITE_APP_EMAIL=john@doe.com- If you are deploying, make sure to add
VITE_APP_EMAILto your deployment environment variables (e.g., in Cloudflare dashboard settings).
To be clear about what this variable does and does not do: it is compiled into the public JavaScript bundle and used purely as UX sugar (pre-filling the login form). Anyone visiting the site can read it, and by itself it restricts nothing. Actual access control is the Firestore rule matching that exact email address (below) — combined with sign-up being disabled so nobody else can ever create an account holding that email.
Update the firebaseConfig object in src/firebase.ts with your project's credentials.
Enable Firestore and set up the security rules. This project includes a firestore.rules file that restricts database access to a single hard-coded owner email:
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null
&& request.auth.token.email == 'your-email@example.com';
}
}
}Replace your-email@example.com with your own address (or mirror the shipped firestore.rules).
Done once in the Google Cloud / Firebase consoles:
- Disable self-registration: Authentication → Sign-in method → Email/Password — disable open sign-up so strangers cannot even create accounts against this project.
- Enable App Check for Firestore and Identity Toolkit.
- Restrict the web API key (Google Cloud Console → Credentials): referrer allowlist limited to your hosting domain, API restrictions limited to Identity Toolkit API and Cloud Firestore API.
- Consider Firestore point-in-time recovery (PITR) as a safety net against accidental writes/deletes.
Configure your db schema as follows:
note: <map>
body: <string>
id: <string>
noteType: <string> // 'QUICK' | 'MARKDOWN'
title: <string>At creation time both id and title are the epoch-milliseconds string (String(Date.now())). Displayed titles are derived from the first non-empty line of the body. Editing a note patches only note.body — id, title, and noteType never change after creation.
Start the local dev server using Vite:
npm install
npm startnpm testRuns the vitest suite (jsdom + Testing Library).
npm install
npm run buildThe output will be generated in the build/ directory, ready to be deployed to Cloudflare Workers (pnpm run deploy).
A scheduled GitHub Action (.github/workflows/backup.yml) runs on the 1st of every month: it dumps the notes collection, encrypts the JSON with age, uploads the encrypted artifact (90-day retention), and deletes the plaintext. Decrypt a downloaded artifact with:
age -d -i ~/.age/notes-backup-private.key -o notes.json backups/notes-YYYY-MM-DD.json.age