# Taragen manual Taragen is a fast, minimal static site generator distributed as a single binary. You write pages in JSX, Markdown, or Go templates, arrange them in folders, and taragen turns the folder into a website. There is no config file, no Node.js, and no `node_modules`: the file tree is the configuration, and JSX runs in a JavaScript engine built into the binary. Why taragen: - **Write in the format that fits.** Markdown for writing, JSX for pages with logic, Go templates if you like them, all in the same site. - **Components without a framework.** JSX layouts and partials let you build a site from reusable pieces, rendered to plain HTML with nothing shipped to the browser. - **Live reloading.** `taragen serve` notices every change and refreshes the browser for you. - **One binary.** Install it and you're done. Nothing to `npm install`, nothing to keep updated. This manual is for people building sites with taragen. It covers every feature, from a first page to generated archives, backlinks, redirects, and share cards, and ends with [tutorials](#tutorials) that put them together. ## Contents 1. [Install](#install) 2. [Quick start](#quick-start) 3. [Commands](#commands) 4. [How a site is laid out](#how-a-site-is-laid-out) 5. [Pages](#pages) 6. [Page data](#page-data) 7. [Defaults and globals](#defaults-and-globals) 8. [Layouts](#layouts) 9. [Partials](#partials) 10. [Writing JSX](#writing-jsx) 11. [Markdown](#markdown) 12. [Go templates](#go-templates) 13. [Listing pages with `pages()`](#listing-pages-with-pages) 14. [Drafts](#drafts) 15. [Static files and assets](#static-files-and-assets) 16. [Non-HTML pages and feeds](#non-html-pages-and-feeds) 17. [Extra outputs per page](#extra-outputs-per-page) 18. [Share cards](#share-cards) 19. [Generated pages](#generated-pages) 20. [Backlinks](#backlinks) 21. [Aliases and redirects](#aliases-and-redirects) 22. [Code highlighting](#code-highlighting) 23. [Building and deploying](#building-and-deploying) 24. [Serve mode and live reload](#serve-mode-and-live-reload) 25. [Tutorials](#tutorials) 26. [Errors and troubleshooting](#errors-and-troubleshooting) 27. [Reference](#reference) ## Install With Homebrew: ```sh brew tap progrium/homebrew-taps brew install taragen ``` Or download a release binary for your platform (macOS, Linux, and Windows, on amd64 and arm64) from the GitHub releases page and put it on your `PATH`. To build from source you need only Go: ```sh make build # writes .local/bin/taragen make link # symlinks it into /usr/local/bin ``` ## Quick start Make a folder for your site, and in it an `index.jsx`: ```jsx

Hello World

``` From that folder, run: ```sh taragen serve ``` Open `http://localhost:8088`. Edit the file and the browser reloads by itself. Add a Markdown page, `about.md`: ```markdown --- title: About --- This site is built with **taragen**. ``` It appears at `/about`. Now give every page the same frame with a root `_layout.jsx`: ```jsx (content) => {page.title ?? "My site"}
{content}
``` Remove the `` and `` from `index.jsx`, since the layout provides them now. When you're ready to publish: ```sh taragen build ``` The finished site is in `_public/`, ready to upload to any static host. ## Commands Both commands work on the current directory as the site root. | Command | What it does | | --- | --- | | `taragen serve` | Serves the site at `http://localhost:8088`, renders pages on request, shows drafts, and live reloads the browser when files change. | | `taragen build` | Cleans `_public/` and writes the whole site into it. Drafts are left out. Also available as `taragen gen` and `taragen generate`. | The port (`8088`) and output directory (`_public`) are fixed. `build` exits with a non-zero status on any error, so it is safe to use in CI and deploy scripts. ## How a site is laid out A site is a folder. Taragen walks it with a few simple rules: - Files and folders whose names start with `.` are ignored. - Files and folders whose names start with `_` are special: they are never pages and are never copied as-is (with the exception of the root `_assets/` folder). This is where layouts, partials, globals, defaults, output templates, and assets live. - Files ending in `.jsx`, `.md`, or `.tmpl` are **page sources**. - Folders are pages too (see [Folders as pages](#folders-as-pages)). - Everything else is a **static file** and is copied to the same path in the output. A path segment written as `[name]` is a page template parameter; see [Generated pages](#generated-pages). ### Special files | File | Purpose | | --- | --- | | `_layout.jsx` / `_layout.tmpl` | Wraps every page in its folder and below. See [Layouts](#layouts). | | `_globals.jsx` | Values and helper functions available to all JSX in its folder and below. See [Defaults and globals](#defaults-and-globals). | | `_defaults.jsx` | Default page data for pages in its folder and below. | | `_outputs/.jsx` | An extra output for every HTML page in its folder and below, such as a `.txt` version or a share card. See [Extra outputs per page](#extra-outputs-per-page). | | Any other `_name.jsx` / `_name.tmpl` | A partial, included with `partial("_dir/name")`. See [Partials](#partials). | | `_assets/` (root only) | Copied verbatim to `_public/_assets/`, never rendered. | | `_redirects` (root only) | Redirect rules in Netlify's format. See [Aliases and redirects](#aliases-and-redirects). | A typical site looks like this: ``` mysite/ ├── _assets/ │ ├── fonts/Recursive.woff2 │ └── style.css ├── _globals.jsx ├── _defaults.jsx ├── _layout.jsx ├── _outputs/ │ ├── txt.jsx │ └── card.png.jsx ├── _partials/ │ └── footer.jsx ├── _redirects ├── index.jsx ├── about.md ├── articles/ │ ├── _layout.jsx │ ├── index.jsx │ ├── feed.xml.jsx │ └── 2021/ │ └── ngrok.md └── tags/ └── [tag].jsx ``` ### Page paths and URLs A page's URL comes from its file path: strip the extension, strip a trailing `/index`, and the root becomes `/`. | Source | URL | Built file | | --- | --- | --- | | `index.jsx` | `/` | `_public/index.html` | | `about.md` | `/about` | `_public/about/index.html` | | `about/index.jsx` | `/about` | `_public/about/index.html` | | `articles/2021/ngrok.md` | `/articles/2021/ngrok` | `_public/articles/2021/ngrok/index.html` | | `articles/feed.xml.jsx` | `/articles/feed.xml` | `_public/articles/feed.xml` | Pages without an extension get clean URLs: they're written as `/index.html`. A page whose name keeps an extension after the format extension, like `feed.xml.jsx`, is written as-is. `about.jsx` and `about/index.jsx` are the same page. Use the folder form when a page has files of its own, like images, next to it. Blog posts are just pages, and a post with several images or downloads reads well as a folder: ``` articles/2021/ngrok/ ├── index.md the post, at /articles/2021/ngrok ├── diagram.png linked from the post as diagram.png └── tunnel.go ``` ### Folders as pages Every folder is a page node, even without an index file. A folder with no index file has no source and isn't built, but it still groups its children, so `pages("articles")` lists everything in `articles/`, and `year.subpages` works for a `2021/` folder with no index. These source-less nodes are called **grouping nodes** in this manual. ## Pages A page source is a `.jsx`, `.md`, or `.tmpl` file. Each format produces an HTML **body**, which is then wrapped by layouts. | Format | Best for | Page data from | | --- | --- | --- | | JSX (`.jsx`) | Index pages, listings, anything with logic | A leading `data({...})` call | | Markdown (`.md`) | Writing: articles, docs, notes | YAML frontmatter | | Go template (`.tmpl`) | Simple templated pages and layouts | YAML frontmatter | A JSX page: ```jsx data({ title: "Articles" });

{page.title}

    {pages("articles/**").reverse().map(a =>
  • {a.title} {a.date}
  • )}
``` A Markdown page: ```markdown --- title: Building your own Ngrok in 130 lines date: 2021-03-14 tags: [go, networking] --- Ngrok is a tunnel... ``` ## Page data Every page has one data object, exposed as `page` in JSX and Go templates and returned for each page by `pages()`. Frontmatter, `data()` calls, defaults, and taragen's own keys all write into this same object. ### Where data comes from Data is filled in this order, and later writers win: 1. Taragen seeds `path` and `slug` (and `params` for generated pages), and sets `outputs`. 2. `_defaults.jsx` files, from the site root down to the page's folder. 3. The page itself: YAML frontmatter in Markdown and templates, or `data({...})` in JSX. 4. Layouts, while rendering: frontmatter in template layouts, `data()` in JSX layouts. After every page is parsed, taragen sets `links` and `backlinks` on every page. ### Built-in keys Any key you set that isn't listed here is yours: `title`, `description`, `tags`, and so on flow through untouched. These keys have built-in meaning: | Key | Set by | Meaning | | --- | --- | --- | | `path` | taragen | The page's URL path, such as `/articles/2021/ngrok`. | | `slug` | taragen | The last segment of the path, such as `ngrok`. | | `src` | taragen | The raw page source (for generated pages, the template's source). | | `body` | taragen | The page's rendered body, before layouts. | | `isDir` | taragen | True for folders and `index.*` files. | | `subpages` | taragen | In `pages()` results: the page's children, sorted, each with its own `subpages`. | | `params` | taragen | Generated pages only: the parameter values, such as `{ year: "2026", month: "11" }`. | | `outputs` | you, then taragen | You set it to turn outputs off; taragen replaces it with a map of output name to URL. See [Extra outputs per page](#extra-outputs-per-page). | | `links` | taragen | Sorted list of the site pages this page's body links to. | | `backlinks` | taragen | Pages whose bodies link to this page. See [Backlinks](#backlinks). | | `aliases` | you | Old URLs that redirect to this page. Taragen replaces it with the normalized list. See [Aliases and redirects](#aliases-and-redirects). | | `siteURL` | you | The site's absolute origin, such as `https://example.com`. Used to recognize absolute links to your own pages, and handy for building absolute URLs in feeds and meta tags. | | `layout` | you | Layout path to use instead of the folder layouts, such as `_blog/_layout`. `""` turns layouts off. | | `date` | you | `YYYY-MM-DD`. Orders listings; a future date makes the page a draft. | | `draft` | you | `true` makes the page a draft. | | `contentType` | you | The HTTP `Content-Type` in `serve`. Defaults to `text/html`. | | `preamble` | you | Text written before the output. Defaults to `` for HTML. | Keys set by taragen replace whatever a page sets for them. Avoid using `outputs`, `links`, `backlinks`, `aliases`, or `params` for your own data. ## Defaults and globals Both are JSX files that run as plain scripts. Every bare assignment at the top level becomes an export, so write `siteTitle = "My site"`, not `export` or `const`: ```js // _globals.jsx siteTitle = "My site"; greeting = Hello!; wrap = (text, font, width) => text.split(" ").reduce((lines, word) => { const last = lines[lines.length - 1]; if (last && measureText(`${last} ${word}`, font) <= width) lines[lines.length - 1] = `${last} ${word}`; else lines.push(word); return lines; }, []); ``` **`_globals.jsx`** defines names that every JSX file in its folder and below can use: pages, layouts, partials, and output templates. Use it for site constants and helper functions. Globals files run from the root down, so a nested `_globals.jsx` sees and can override what its parents defined. Nested globals are merged, so to hide a parent's global in a section, set it to `undefined` in that section's `_globals.jsx`. Since globals files are JSX, a global can be markup, like `greeting` above, and used anywhere as `{greeting}`. Globals are for JSX. Markdown and Go template pages can't read them directly; to use one there, wrap it in a JSX partial and include the partial with `[[ partial "_partials/name" ]]`. **`_defaults.jsx`** sets default page data for every page in its folder and below. Its exports are merged into each page's data before the page's own frontmatter or `data()`, so pages can override them. `page` is available in it, so defaults can depend on the page: ```js // _defaults.jsx siteURL = "https://example.com"; // articles/_defaults.jsx aliases = [page.path.replace(/^\/articles/, "/blog")]; ``` Built-in globals (`page`, `data`, `partial`, `raw`, `pages`, `params`, `measureText`) are added after your globals, so they always win. Don't define globals with those names. Every JSX render runs in a fresh JavaScript VM, and globals are re-run for each one. Pages can't leak state into each other, so don't try to share mutable state through globals. ## Layouts A layout wraps pages. A `_layout.jsx` is a function of one argument, the content to wrap: ```jsx (content) => {page.title} · {siteTitle} {partial("_partials/header")}
{content}
{partial("_partials/footer")} ``` `content` is already-rendered HTML and is inserted as is. `page` is the data of the page being wrapped. ### Nesting A layout applies to pages in its folder and every folder below. Layouts nest nearest first: a page in `articles/2021/` is wrapped by `articles/_layout.jsx`, and the result is wrapped by the root `_layout.jsx`. Section layouts usually return a fragment or a `
`, and leave `` to the root layout: ```jsx // articles/_layout.jsx (content) =>

{page.title}

{content}
``` ### Choosing a layout explicitly Set `layout` in a page's data to skip the folder layouts and use a specific one. The value is a path from the site root, without the extension: ```markdown --- layout: about/_wide --- ``` `layout: ""` turns layouts off entirely, which is what feeds and other non-HTML pages want. A layout can also set `layout` (with `data()` in JSX, or frontmatter in a template layout). When it does, the remaining folder layouts are dropped and the named layout is applied next. This lets one layout hand off to a specific chain: ```jsx // about/_layout.jsx data({ layout: "_layouts/wide" }); (content) =>
{content}
``` Go template layouts (`_layout.tmpl`) get the content through `[[ content ]]`. ## Partials Partials are reusable pieces of markup you can include in pages, layouts, and other partials. Any `_name.jsx` or `_name.tmpl` file that isn't one of the special files above is a partial, and so is any `.jsx` or `.tmpl` file inside an `_`-prefixed folder. Most sites keep them together in a root `_partials/` folder, where the files don't need their own underscore. Include a partial from JSX with its path from the site root, without the extension: ```jsx {partial("_partials/footer")} ``` A navigation partial: ```jsx // _partials/nav.jsx ``` A partial can be a plain element, as above, or a function. Arguments after the name are passed to the function: ```jsx // _partials/card.jsx (post, showDate = true) =>
{post.title} {showDate && }
``` ```jsx {pages("articles/**").map(p => partial("_partials/card", p))} ``` Partials see the same globals and `page` as the file that calls them. `.jsx` is tried first, then `.tmpl`. A missing partial, or one that throws, fails the page with an error naming it. In Markdown and Go templates, use `[[ partial "_partials/footer" ]]`. Template partials (`.tmpl`) currently receive no helper functions and ignore arguments, so prefer JSX partials. ## Writing JSX Taragen's JSX is a server-side template language. Elements turn straight into HTML strings; there is no React, no components with state, no hooks, and nothing runs in the browser. Write `class`, not `className`. ### Pages, data, and multiple roots A JSX page can have several root elements, or start with an expression: ```jsx data({ title: "Home" });

Welcome

Latest posts:

{partial("_partials/latest")} ``` A `data({...})` call at the very top of the file (after whitespace and comments) sets page data. The trailing `;` is optional. `data()` merges its object into both `page` and the page's data, so later code in the file and the layouts see it. In a [generated page template](#generated-pages), a `params(...)` call can sit alongside `data()` in these leading statements. ### Values in JSX Children can be elements, strings, numbers, or arrays (flattened, so `.map(...)` works). `null`, `undefined`, `true`, and `false` render nothing, so conditionals work: ```jsx {page.draft && Draft} {page.tags?.map(t => {t})} ``` Attribute values can be: | Value | Result | | --- | --- | | string | `name="value"`; an empty string omits the attribute | | number | Printed as JS prints it | | `true` / `false` | Bare attribute (``) / omitted | | object | Serialized as a `style` string: `style={{ color: "red", "font-size": "2em" }}` | | `null` / `undefined` | Omitted | Anything else, such as an object as a child or a function as an attribute, throws a `TypeError` and fails the render. Attributes are written in the order you wrote them, and the output is identical on every build, so diffs of `_public/` show only real changes. Void elements like ``, ``, and `
` are self-closed. ### Escaping and `raw()` Text and attribute values are escaped, so a title with `&` or `"` is safe anywhere, including in attributes and XML feeds. Text inside `