DocsWeb development

Building static sites with Lume

Practical Lume 3 setup — init, plugins, _components, and named permissions

Lume is a Deno-first static site generator for documentation, blogs, and content sites. The build output is plain static files that any static host can serve. This very site is built with Lume 3, and the examples below come from its real _config.ts and deno.json.

Create a project

deno run -A https://lume.land/init.ts
cd my-site
deno task lume -s   # dev server with watch and reload
deno task lume      # build into _site/

The official recommendation is project-local setup rather than a global CLI: the init script generates a deno.json containing a lume task and a matching named permission profile. Upgrade with deno task lume upgrade. If a tutorial uses deno.land/x/lume/init.ts or asks you to deno install a global lume command, it predates the current setup — the domain is now lume.land.

Core concepts

Configuration lives in _config.ts: create the site, register plugins, declare shared data.

import lume from "lume/mod.ts";
import jsx from "lume/plugins/jsx.ts";
import mdx from "lume/plugins/mdx.ts";
import sitemap from "lume/plugins/sitemap.ts";

const site = lume({ location: new URL("https://example.com") });

site.use(jsx());
site.use(mdx());
site.use(sitemap({ query: "indexable=true", items: { lastmod: "=lastModified" } }));

site.copy("static", ".");
site.data("layout", "doc.tsx");

export default site;
  • Plugins: Markdown, Vento, search, and pagination work out of the box; JSX, MDX, sitemap, feed, and others are registered on demand with site.use().
  • _includes/: the layouts and template partials directory. Pages pick a layout via the layout frontmatter key, or you can set a site-wide default with site.data("layout", ...).
  • _components/: reusable components available in every template through the global comp variable, and engine-agnostic — a component written in JSX can be used from Vento. The Callout at the bottom of this page is one such component.
  • preprocess vs process: site.preprocess([".mdx"], ...) runs before rendering and is good for injecting data — this site uses it to set locale and lastModified on every page. site.process([".html"], ...) runs after rendering with access to page.document for DOM work — this site uses it to wrap tables in scroll containers and add rel="noreferrer" to external links.

Narrowing permissions

Lume needs to read source files, write _site/, and fetch dependencies over the network, so many tutorials reach for -A. Deno 2 named permission profiles let you narrow and pin the permissions instead. This site's deno.json is a complete example:

{
  "tasks": {
    "lume": "deno run -P=lume lume/cli.ts"
  },
  "permissions": {
    "lume": {
      "read": true,
      "write": true,
      "import": ["cdn.jsdelivr.net:443", "jsr.io:443", "deno.land:443"],
      "net": ["cdn.jsdelivr.net:443", "jsr.io:443", "deno.land:443", "esm.sh:443"],
      "env": true,
      "run": true,
      "ffi": true,
      "sys": true
    }
  }
}

deno run -P=lume applies only the profile named lume and nothing else. To tighten further: restrict net/import to the hosts your dependencies actually come from (as above), narrow write from true to ["_site"], and narrow env to the variables the build really reads (e.g. ["SITE_URL"]). Then run deno task lume — Deno reports exactly which permission is missing if you went too far.

Build and deploy

deno task lume emits _site/, a fully static artifact: Nginx, object storage, Cloudflare Pages, and GitHub Pages all host it directly. The new Deno Deploy has first-class static site support and detects Lume projects automatically with no extra configuration. Note that Deploy Classic does not support static hosting — the older "serve files with Deno.serve" recipes only apply to Classic.

Lume or Fresh

Consistent with the decision table: if every page is knowable at build time (docs, blogs, marketing pages), pick Lume; if you need request-time rendering, database access, or interactive islands, pick Fresh. Both can ship near-zero client JavaScript — the difference is when the HTML is generated.

Official references: Lume installation, Processors, Components, Plugin list, and Deno Deploy framework support.

Type to search all documentation.