Skip to content

Architecture

Plate is a read-only consumer of the Snackbox REST API — it fetches content, renders Markdown to HTML via a Remark/Rehype pipeline, and serves the result through a Handlebars theme. It has no database, no authentication, and no admin interface.

For deployment and operations, see docs/ADMINISTRATOR.md. For contributing to the codebase, see docs/DEVELOPER.md. For building a custom theme, see docs/THEME-DEVELOPER.md.


Deployment topology

graph TD
    Browser["Browser"]
    Caddy["Caddy\n(80 / 443)"]
    Plate["Plate\n(3000)"]
    Snackbox["Snackbox\n(8080)"]
    Prometheus["Prometheus"]
    Metrics["Metrics endpoint\n(9091)"]

    Browser -->|"HTTPS"| Caddy
    Caddy -->|"/media/*"| Snackbox
    Caddy -->|"all other requests"| Plate
    Plate -->|"REST API"| Snackbox
    Prometheus -->|"scrape"| Metrics
    Plate -->|"exposes"| Metrics
Port Component Notes
80 / 443 Caddy Public-facing; TLS termination; routes /media/* to Snackbox
3000 Plate HTTP only; bind to 127.0.0.1 or Docker network — never expose publicly
8080 Snackbox REST API and media files
9091 Plate metrics Prometheus scrape target; bind to 127.0.0.1 — never expose publicly

Caddy is optional but recommended. Any reverse proxy that can terminate TLS and forward headers works. The /media/* proxy to Snackbox is required only if media assets are served from the same domain as Plate.


Application layers

graph TD
    Request["Incoming request"]

    subgraph Middleware
        Helmet["Helmet\n(CSP, security headers)"]
        Compression["Compression"]
        RateLimit["Rate limiter"]
        ReqMetrics["Request metrics\n(prom-client)"]
        AccessLog["Access log\n(pino-http)"]
        GlobalCtx["Global context\n(settings, nav, tags, social)"]

        Helmet --> Compression --> RateLimit --> ReqMetrics --> AccessLog --> GlobalCtx
    end

    subgraph Routes
        Home["Home"]
        Posts["Posts"]
        Pages["Pages"]
        Tags["Tags"]
        Health["Health"]
    end

    subgraph Core
        SnackboxClient["SnackboxClient\n(HTTP + SimpleCache)"]
        Renderer["Renderer\n(Remark → rehype → HTML)"]
        ThemeManager["ThemeManager\n(hot-reload, fallback)"]
    end

    Request --> Middleware
    Middleware --> Routes
    Routes --> Core

Key design decisions

SnackboxClient + SimpleCache — All Snackbox API calls go through a single client singleton. Admin-controlled data (settings, navigation, social accounts, tags) is cached with a short 15 s TTL. Content (posts, pages) is cached with a 60–300 s TTL, scalable via CACHE_TTL_MULTIPLIER. Snackbox 1.2.0 will add webhook-based invalidation, replacing the short TTL for admin resources.

Theme hot-reload — The globalContext middleware compares the active theme name from Snackbox settings against the currently loaded theme on every request. On mismatch it awaits ThemeManager.reload() before rendering. Concurrent reload calls coalesce on a single in-flight Promise. Setting THEME_NAME disables hot-reload entirely.

Renderer — Markdown is converted to sanitized HTML via a Remark/Rehype pipeline. rehype-sanitize strips unsafe HTML before the output reaches any template.

Two HTTP servers — Plate starts a main Express server (port 3000) and a separate plain HTTP server for the Prometheus metrics endpoint (port 9091). This keeps metrics off the public request path without routing overhead.

src/fixture/ — A built-in Snackbox fixture server (startFixture / stopFixture) backed by realistic edge-case content. It uses Node.js built-in http only (no Express), ships in dist/, and is the single source of truth for integration tests, the benchmark, and theme:dev.

src/cli/ — The plate CLI entry point (npx plate theme:*). Thin wrappers around existing lib functions: validateTheme for validation, startFixture for live preview. The bin.plate field in package.json points here.