Sign inSign up

dihechen/pageshot

By dihechen

•Updated 4 months ago

Render markdown — or raw HTML — into beautiful, themeable images via Playwright.

Image
0

453

dihechen/pageshot repository overview

⁠pageshot

English | 简体中文⁠

Render markdown — or raw HTML — into beautiful, themeable images via Playwright.

pageshot is a self-contained HTTP / Unix Socket service: send it a markdown document (or pre-styled HTML) and get back a high-DPI PNG, JPEG or WebP — complete with LaTeX math, syntax highlighting and responsive layout.

⁠Features

  • Adaptive sizing — any markdown length renders as one image; viewport grows naturally
  • Markdown or raw HTML — pass markdown for the themed pipeline, or html to render your own pre-styled markup
  • Five beautiful themes — GitHub Light/Dark, Notion, Academic, Dracula
  • Crystal-clear text — deviceScaleFactor: 2 (retina) by default
  • Full LaTeX support — inline $...$ and block $$...$$ formulas via KaTeX
  • Extensive code highlighting — 40+ languages via Shiki (VS Code engine)
  • Dual protocol — HTTP + Unix socket in the same process
  • Production-ready — warmup at startup, concurrency limits, graceful shutdown

⁠Security notice

  • Run this service only in a trusted environment (for example, internal network / trusted callers).
  • Rendering input is not fully sanitized (especially raw html), and renderer content may request external resources.
  • Do not expose this service directly to untrusted public input without an additional isolation/sanitization layer.

⁠Quick start

# Install dependencies and browser
pnpm install
pnpm playwright:install

# Start the server (watches for changes in dev mode)
pnpm dev

In another terminal:

# HTTP — synchronous markdown render
curl -X POST http://localhost:12000/images \
  -H 'Content-Type: application/json' \
  -d '{"markdown": "# Hello\n\n$$E=mc^2$$", "theme": "github-light"}' \
  -o hello.png

# HTTP — render raw HTML (no theme/KaTeX CSS — caller controls all styling)
curl -X POST http://localhost:12000/images \
  -H 'Content-Type: application/json' \
  -d '{"html": "<div style=\"font-family:sans-serif\"><h1>Hi</h1></div>"}' \
  -o html.png

# Unix Socket (fastest for local use)
curl --unix-socket /var/run/pageshot.sock \
  -X POST http://localhost/images \
  -H 'Content-Type: application/json' \
  -d '{"markdown": "# Hello\n\n$\\int_0^1 x \\,dx = \\frac{1}{2}$", "theme": "dracula"}' \
  -o hello.png

⁠Themes

ThemeStyle
github-lightClassic GitHub README (light)
github-darkClassic GitHub README (dark)
notionClean, modern, generous spacing
academicSerif, paper-like, compact
draculaDark with vibrant accents

Preview all of them in the themes documentation⁠.

⁠API

Interactive documentation is available at http://localhost:12000/documentation once the server is running.

⁠POST /images

Synchronous render. Content negotiation via the Accept header:

Accept headerResponse
(default / omitted)Raw image binary + metadata in HTTP headers
application/jsonMetadata JSON only (no image bytes)
multipart/mixedMetadata JSON + image binary in a multipart body

Request body (exactly one of markdown or html is required)

FieldTypeDefaultDescription
markdownstring(one of)Markdown source. Mutually exclusive with html.
htmlstring(one of)Raw HTML body. Rendered without theme/KaTeX CSS. Trusted input — not sanitized.
themestringgithub-lightOne of the theme names above. Ignored when html is supplied.
widthnumber800Content width in px (300–2000)
formatstringpngpng, jpeg, webp
scalenumber2Retina factor (1, 2, 3)
qualitynumber90JPEG/WebP quality (1–100)
paddingnumber40Inner padding in px (0–160)

Example (raw image)

curl -X POST http://localhost:12000/images \
  -H 'Content-Type: application/json' \
  -d @examples/sample.json \
  -o out.png

Example (JSON metadata only)

curl -X POST http://localhost:12000/images \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{"markdown": "# Hi"}' | jq '.height'
⁠Asynchronous rendering

For large documents or when you don't want to block the connection:

# Create a render job
curl -X POST http://localhost:12000/renders \
  -H 'Content-Type: application/json' \
  -d '{"markdown": "# Hello", "theme": "notion"}'
# → {"id":"...","status":"queued","location":"/renders/..."}

# Poll for completion
curl http://localhost:12000/renders/{id}
# → {"status":"completed","result":{"imageUrl":"/renders/{id}/image",...}}

# Fetch the image
curl http://localhost:12000/renders/{id}/image -o out.png
⁠GET /themes
{ "themes": [
  { "name": "github-light" },
  { "name": "github-dark" },
  { "name": "notion" },
  { "name": "academic" },
  { "name": "dracula" }
]}
⁠GET /health

Returns { "status": "ok", "browser": "ready" } once the render pipeline is warmed up.

See docs/api.md⁠ for full details.

⁠Environment variables

VariableDefaultPurpose
PAGESHOT_PORT12000HTTP port (0 to disable HTTP)
PAGESHOT_HOST0.0.0.0HTTP bind address
PAGESHOT_SOCKET/var/run/pageshot.sockUnix socket path (empty to disable)
PAGESHOT_CONCURRENCY4Max parallel Playwright contexts
PAGESHOT_LOCALEzh-CNBrowser locale (affects font fallback)
PAGESHOT_CACHE_MAX_ENTRIES50LRU cache: max number of cached renders
PAGESHOT_CACHE_MAX_BYTES_MB100LRU cache: total bytes ceiling (MB)
PAGESHOT_CACHE_TTL_SECONDS300LRU cache: time-to-live per entry (seconds)
PAGESHOT_JOB_TTL_SECONDS600Async job: retention before eviction (seconds)
PAGESHOT_JOB_MAX_COUNT1000Async job: maximum stored jobs
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH—Custom Chromium binary path

⁠Docker

docker build -t pageshot .
docker run -p 12000:12000 -e PAGESHOT_CONCURRENCY=4 pageshot

See docs/deployment.md⁠ for systemd, health-check and tuning details.

⁠License

MIT

Tag summary

Content type

Image

Digest

sha256:09275c792…

Size

315.4 MB

Last updated

4 months ago

docker pull dihechen/pageshot