# Building static sites with Lume

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

```bash
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.

```ts title="_config.ts (excerpt from this site's actual config)"
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:

```json title="deno.json (excerpt)"
{
  "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](/en/docs/web): 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.

<Callout type="info" title="Fit boundary">
Lume components run only at build time and send no runtime code to the browser. Once interactivity (form state, client-side routing) becomes the core of the product, evaluate Fresh instead of stacking `<script>` tags on static pages.
</Callout>

Official references: [Lume installation](https://lume.land/docs/overview/installation/), [Processors](https://lume.land/docs/core/processors/), [Components](https://lume.land/docs/core/components/), [Plugin list](https://lume.land/plugins/?status=all), and [Deno Deploy framework support](https://docs.deno.com/deploy/reference/frameworks/).
