Skip to content

Repository files navigation

doogie

A tiny, client-only reader that turns a folder of markdown files into a beautifully typeset website. No build step for your writing, no database, no server logic — an HTTP server hands over .md files and doogie typesets them.

  • Typography first — Literata body text on the Everforest palette, fluid 17→20 px sizing, ~70 ch measure, stepped line-height, real small caps, oldstyle figures in prose with tabular figures in tables, smart punctuation.
  • Yours to change — eight color schemes and fourteen typefaces from a panel in the sidebar, plus light/dark/auto. Deployers set site defaults (and may supply their own palette and fonts) with a single doogie.json; no rebuild. See Customizing doogie.
  • Live outline — a sidebar table of contents scanned from h2/h3 (the lone h1 is the title, not a section), highlighting the section you're reading; collapsible with [.
  • VS Code-quality highlighting — Shiki v4 with the Everforest Dark/Light themes, grammars lazy-loaded per language; a document without code downloads zero highlighter bytes.
  • Anchors that work — GitHub-compatible heading slugs, hover permalinks, deep links that survive late-loading images and code colorization.
  • Bookmarks & reading position — stored per document in localStorage; doogie takes you back to where you left off (a URL #hash always wins).
  • Every font reads the same size — font-size-adjust pins x-height, so switching faces changes the character of the page and not how large it reads.
  • Reading controls — sliders for text size (document only — the interface never moves), text weight (a live 300–700 range on the variable faces) and, on faces with a width axis, text width — for the body and, separately, for headings, which can also take a face of their own; plus font smoothing and a full-measure toggle for wide tables, from the same drawer.
  • Remote mode — ?src=<url> reads any CORS-accessible markdown file (raw GitHub files, gists…).

The URL is the API

Deploy the built app and your .md files on the same host:

You visit doogie fetches
md.example.com/notes /notes.md
md.example.com/guides/setup /guides/setup.md
md.example.com/ or /guides/ index.md there (root falls back to a built-in welcome page)
md.example.com/?src=https://…/README.md that remote file

#hash fragments are heading anchors, exactly like GitHub's.

Deploying

Build once, then copy dist/ and your markdown to any static host:

pnpm install
pnpm build
rsync -av dist/ server:/srv/doogie/
rsync -av ~/writing/*.md server:/srv/doogie/

One server rule makes clean URLs work: serve real files as-is; every other path returns index.html with status 200.

Netlify — nothing to do: a _redirects file with /* /index.html 200 ships in the build. Non-forced rewrites only fire when no real file matches.

Cloudflare Pages — nothing to do; the same _redirects file (or Pages' default SPA behavior) covers it.

nginx

server {
  root /srv/doogie;
  index index.html;
  charset utf-8;

  location /assets/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
  }

  # optional but nice: direct .md visits display instead of downloading
  location ~* \.md$ {
    default_type "text/markdown; charset=utf-8";
    try_files $uri =404;
    add_header Cache-Control "no-cache";
  }

  location / {
    try_files $uri /index.html;
  }
}

Caddy

md.example.com {
  root * /srv/doogie
  encode zstd gzip
  try_files {path} /index.html
  file_server
}

GitHub Pages — no rewrites exist; copy index.html to 404.html in the deployed folder. Misses then return HTTP 404 status but the reader works. Remote ?src= mode is unaffected.

Notes:

  • doogie never trusts the .md content type (hosts serve markdown as anything from text/markdown to application/octet-stream); it sniffs HTML responses to detect misses behind the SPA fallback instead.
  • Deploying under a subpath? Set Vite's base in vite.config.ts and rebuild — routing follows import.meta.env.BASE_URL.
  • Browser floor: current evergreen (Chrome 139+, Firefox 142+, Safari 26+) — set in vite.config.ts (css.lightningcss.targets), so the modern CSS (light-dark(), range media queries, allow-discrete) ships untranspiled.

Developing

pnpm dev        # dev server; sample docs live in public/
pnpm test       # vitest unit suite
pnpm check      # svelte-check (strict TypeScript)
pnpm build      # production build to dist/
pnpm preview    # serve the production build locally

Sample torture documents: public/sample.md (languages, duplicate headings, wide tables, late images) and public/guides/setup.md (nested paths, relative links). The bundled welcome page (src/welcome.md) doubles as the typography test sheet.

How it's put together

Svelte 5 (runes) + Vite + strict TypeScript. Four components; framework-free logic in src/lib/.

  • Pipeline: markdown-it 15 (typographer on) + anchor/footnote/task-list plugins → one DOMPurify pass (the security boundary — documents can come from arbitrary URLs) → {@html}. Code blocks render instantly as plain <pre>, then a lazily-imported Shiki core swaps in dual-theme Everforest highlighting, yielding to the main thread between blocks.
  • Scroll-spy: rAF over cached heading offsets with a click-lock; its activation line equals the CSS scroll-margin-top token, so the spy and anchor scrolling can never disagree. A ResizeObserver plus document.fonts.ready handle late layout shifts.
  • Appearance: <html> carries the whole state as attributes (data-mode, data-scheme, data-font-*), CSS does all the switching, and an inline script in index.html applies saved settings before first paint so returning readers never see a flash. Colors live in one semantic token layer, so a scheme is just a block that redefines it — the default scheme being the :root block itself. A test fails the build if a scheme forgets a token, or if any file consumes one that is never defined.
  • Untrusted by default: doogie.json is validated field by field — colors are shape-checked before CSS.supports, font URLs are protocol-limited, and anything unusable falls back to a built-in rather than breaking the page.

About

the best MD for your .md's

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages