A self-hosted markdown blog system built with SvelteKit and Bun. Designed for personal writing and content management, with a focus on simplicity and performance.






| Category | Technology |
|---|---|
| Framework | SvelteKit (SSR) |
| Runtime | Bun |
| Database | SQLite via bun:sqlite |
| Styling | SCSS + clsx |
| Fonts | SUIT · Noto Sans SC |
| Markdown Editor | CodeMirror 6 + svelte-codemirror-editor |
| Markdown Renderer | marked + highlight.js |
| Type Checking | TypeScript |
# Clone the repository
git clone https://github.com/aolose/emm.git
cd emm
# Install dependencies
bun install
# Build for production
bun run build
# Start the server
bun run preview
The application will be available at http://localhost:4173.
| Variable | Default | Description |
|---|---|---|
PORT |
4173 |
Server listen port |
ORIGIN |
http://localhost:4173 |
Public origin URL (required for RSS / sitemap) |
Set them before starting the server:
PORT=3000 ORIGIN=https://blog.example.com bun run preview
On first run, visit the /config page to set up your admin username and password. No registration system — single admin account.
All settings are managed through the Admin UI (/admin/setting), stored in the SQLite database:
Runtime files and directories:
data/upload)data/thumb)bun install
bun run build
# The built output is in the `dist/` directory
bun run dist/index.js
For long-running production use, consider a process manager:
# systemd example (/etc/systemd/system/emm.service)
[Unit]
Description=EMM Blog
After=network.target
[Service]
Type=simple
User=emm
WorkingDirectory=/home/emm/app
Environment=PORT=3000
Environment=ORIGIN=https://blog.example.com
ExecStart=bun run dist/index.js
Restart=on-failure
[Install]
WantedBy=multi-user.target
Or with PM2:
pm2 start dist/index.js --name emm --interpreter bun
EMM integrates Cloudflare Turnstile CAPTCHA and includes a built-in search engine crawler whitelist bypass logic.
If you use Cloudflare proxy and have Turnstile enabled, you need to configure a Transform Rule to ensure SEO crawlers are not blocked.
For detailed steps, see: doc/turnstile.md
Security Reminder: Make sure to protect your origin server to prevent attackers from bypassing Cloudflare and directly accessing the origin IP to forge request headers. It is recommended to use Cloudflare Tunnel or restrict your origin firewall to only accept Cloudflare IP ranges.
Issues and pull requests are welcome. Before submitting a PR:
git checkout -b feature/your-feature)MIT © Aolose