https://github.com/techjewel/mdread
A quiet reading room for your markdown β local-first, privacy-first reader. Drop a file or folder, read, edit, download. No backend. Live: https://mdread.app
https://github.com/techjewel/mdread
cloudflare-workers local-first markdown markdown-editor markdown-reader offline-first privacy pwa
Last synced: 12 days ago
JSON representation
A quiet reading room for your markdown β local-first, privacy-first reader. Drop a file or folder, read, edit, download. No backend. Live: https://mdread.app
- Host: GitHub
- URL: https://github.com/techjewel/mdread
- Owner: techjewel
- License: mit
- Created: 2026-05-27T14:45:17.000Z (24 days ago)
- Default Branch: main
- Last Pushed: 2026-05-27T14:46:33.000Z (24 days ago)
- Last Synced: 2026-05-27T16:25:11.378Z (24 days ago)
- Topics: cloudflare-workers, local-first, markdown, markdown-editor, markdown-reader, offline-first, privacy, pwa
- Language: JavaScript
- Homepage: https://mdread.app
- Size: 855 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# mdread
**A quiet reading room for your markdown.** Drop a file or a whole folder, read it
beautifully, edit it in place, and download it β all in the browser. Local-first:
your files never leave your device. Deploys to Cloudflare in one command.
π **Live: [mdread.app](https://mdread.app)** Β· MIT licensed Β· no backend Β· no tracking
[](https://deploy.workers.cloudflare.com/?url=https://github.com/techjewel/mdread)

## What it does
- π **Open a folder or files** β via the native file picker, or just **drag & drop**
anything onto the page (a single `.md`, a stack of files, or an entire folder tree).
- π **Read** β typography tuned for hours of long-form reading, using your OS's
native reading serif (New York on Apple, Georgia elsewhere β no web fonts to
download): comfortable measure and leading, oldstyle figures, a table of contents
with scroll-spy, and a reading-progress bar.
- βοΈ **Edit** β Read / Split / Edit views. On Chrome & Edge, **Save writes straight
back to the original file on disk** (File System Access API). Elsewhere it downloads.
- β¬οΈ **Download** any document as a clean `.md`.
- π¨ **Day / Sepia / Night** themes, adjustable text size, line width, typeface, and
an optional drop cap.
- π **Offline** β installable PWA; the app shell is cached, so it works with no network.
- π§ **Remembers** your last folder, your last document, your reading position, and your
preferences across visits.
Everything runs client-side with three small markdown libraries
([marked](https://marked.js.org), [DOMPurify](https://github.com/cure53/DOMPurify),
[highlight.js](https://highlightjs.org)) bundled and self-hosted, and **system fonts
only** β no external requests at runtime.
## Develop locally
```bash
npm install
npm run dev # Vite dev server with hot reload β http://localhost:5173
```
Edit anything under `src/` and the page reloads. To check a production build the
way it's actually deployed:
```bash
npm run build # bundles + compiles SCSS into ./dist
npm run preview # serves ./dist β http://localhost:4173
```
> **Note on editing:** live "save to disk" needs the File System Access API
> (Chrome/Edge, and over `http://localhost` or HTTPS). In other browsers files open
> read-only and edits download as new files. Reading works everywhere.
## Deploy to Cloudflare
### One-click (no CLI)
[](https://deploy.workers.cloudflare.com/?url=https://github.com/techjewel/mdread)
Cloudflare clones this repo into your own GitHub, runs `npm run build`, and deploys
to a free `*.workers.dev` URL on your account. Every push to your copy then
auto-builds and deploys via Workers Builds. (The `mdread.app` custom domain lives in
the `production` environment, which the button doesn't use β you get your own URL.)
### From the CLI
Both paths below build first, then publish `./dist`.
#### Workers (recommended, uses `wrangler.jsonc`)
```bash
npm install
npx wrangler login # or set CLOUDFLARE_ACCOUNT_ID for non-interactive / CI deploys
npm run deploy # β a free *.workers.dev URL (what a fork gets out of the box)
npm run deploy:prod # β your custom domain (uses the `production` env in wrangler.jsonc)
```
`wrangler.jsonc` doesn't hardcode an account or domain: the account comes from
`wrangler login` (or `CLOUDFLARE_ACCOUNT_ID`). The base config publishes to
`*.workers.dev`; the `env.production` block carries the custom domain, so
`deploy:prod` is the one that goes live on a real domain. To use your own domain,
change `name` and the `pattern` under `env.production.routes`.
#### Cloudflare Pages
```bash
npm run build
npx wrangler pages deploy dist --project-name markread
# or: npm run deploy:pages
```
β¦or in the Cloudflare dashboard: **Pages β Create β Connect/Direct upload**. Build
command `npm run build`, output directory `dist`.
## Keyboard shortcuts
| Key | Action |
| --- | --- |
| `β/Ctrl + O` | Open folder |
| `β/Ctrl + S` | Save (to disk, or download) |
| `β/Ctrl + E` | Toggle edit |
| `β/Ctrl + \` | Toggle sidebar |
| `t` | Toggle table of contents |
| `f` | Focus mode |
| `/` | Search files |
| `Esc` | Exit focus / close popover |
## Project layout
```
index.html app shell (Vite entry)
src/
main.js entry: imports styles, wires the UI, boots the app
modules/ one concern per file β files, tree, document, editor,
save, markdown, recents, scroll, view, ui, keyboard, β¦
styles/ SCSS partials assembled by main.scss (themes, typography, layout)
public/ static assets copied verbatim: icons, manifest, og-image
vite.config.js build + PWA service-worker config
wrangler.jsonc Cloudflare Workers Assets config (serves ./dist)
```
No framework β plain ES modules and SCSS. The service worker is generated by
`vite-plugin-pwa`. See [`CLAUDE.md`](CLAUDE.md) for an architecture tour and
[`CONTRIBUTING.md`](CONTRIBUTING.md) to get started.
## Privacy
There is no server, no analytics, and **no external requests at runtime** β not
even web fonts (the app uses your operating system's native fonts). Files are read
in your browser; the only persistence is local (IndexedDB stores folder handles so
they can be reopened; `localStorage` stores preferences and reading positions).
## Contributing
Issues and PRs welcome β see [`CONTRIBUTING.md`](CONTRIBUTING.md) and the
[`Code of Conduct`](CODE_OF_CONDUCT.md). The app is plain ES modules in `src/modules/`
and SCSS in `src/styles/`, so it's quick to find your way around.
## License
[MIT](LICENSE) Β© techjewel