Lightweight static sites. Everything built in.

Stati is a TypeScript-first static site generator. Write Markdown and Eta templates, and get typed config, client bundles, search, feeds, SEO and incremental rebuilds without assembling a plugin stack.

npx create-stati
  • 9 core dependencies
  • No client runtime
  • Node 22+
  • MIT
stati.config.ts
import { defineConfig } from '@stati/core';

export default defineConfig({
  site: { title: 'Field Notes', baseUrl: 'https://example.com' },
  typescript: {
    enabled: true,
    bundles: [
      {
        entryPoint: 'search.ts',
        bundleName: 'search',
        include: ['/docs/**'],
      },
    ],
  },
  isg: { enabled: true, ttlSeconds: 21600 },
  search: { enabled: true },
  sitemap: { enabled: true },
});

Everything a content site needs, in the box

Every feature below ships in Stati’s own packages and is documented here. Most switch on with one line in a typed config file. No plugin hunting, no glue code.

Write

Build

Publish

Develop

28 built-in features across @stati/core, @stati/cli and create-stati

Small to learn, typed end to end, quick to rebuild

Markdown and Eta. Nothing else to learn.

Pages are Markdown files with front matter. Layouts are Eta templates, plain HTML with JavaScript where you need it. No framework components, no hydration, and nothing shipped to the browser unless you add it.

How templates work
site/blog/hello.md
---
title: Hello, Stati
tags: [news]
---

# Hello, Stati

Write in **Markdown**. The layout does the rest.
site/layout.eta
<!doctype html>
<html lang="en">
  <head><title><%= stati.page.title %></title></head>
  <body>
    <%~ stati.partials.header %>
    <main><%~ stati.content %></main>
  </body>
</html>

TypeScript from config to client.

Configuration is a typed object, so your editor completes every option. Client code is TypeScript too: Stati compiles it with esbuild, hashes it for production, and injects each bundle only on the pages that match.

TypeScript bundles
stati.config.ts (excerpt)
export default defineConfig({
  typescript: {
    enabled: true,
    bundles: [
      // On every page
      { entryPoint: 'main.ts', bundleName: 'main' },
      // Only where it's needed
      {
        entryPoint: 'search.ts',
        bundleName: 'search',
        include: ['/docs/**'],
      },
    ],
  },
});

Rebuild only what changed.

Incremental Static Generation keeps a build cache. A page is rebuilt when its content or templates change, or when its TTL runs out, and aging rules cache older pages for longer. Clear anything on demand by tag, path or age.

Incremental Static Generation
stati.config.ts (excerpt)
isg: {
  enabled: true,
  // New articles: every 5 minutes
  ttlSeconds: 300,
  maxAgeCapDays: 30,
  aging: [
    // Then hourly, then daily
    { untilDays: 1, ttlSeconds: 3600 },
    { untilDays: 7, ttlSeconds: 86400 },
  ],
},
terminal
npx stati invalidate "tag:news"
stati build 4 rebuilt · 2 cached
Illustrative next build after invalidating tag:news
PageResultReason
/ within TTL cached
/news/launch-day/ invalidated by tag:news rebuilt
/news/roadmap/ invalidated by tag:news rebuilt
/blog/2025-recap/ aged: cached for a day cached
/docs/setup/ docs/layout.eta changed rebuilt
/about/ TTL expired rebuilt

Illustrative next build after invalidating tag:news

This site is the demo

stati.build is built with Stati, plus Tailwind for CSS, Prism for code highlighting and FlexSearch behind the search box. The search index, sitemap, robots.txt, RSS feed and per-page script bundles all come from the config below.

View the source on GitHub (opens in a new tab)
docs-site/stati.config.ts (excerpt)
typescript: {
  enabled: true,
  bundles: [
    { entryPoint: 'core.ts', bundleName: 'core' },
    {
      entryPoint: 'home.ts',
      bundleName: 'home',
      include: ['/index.html', '/'],
    },
    {
      entryPoint: 'docs.ts',
      bundleName: 'docs',
      exclude: ['/index.html', '/'],
    },
  ],
},
isg: { enabled: true, ttlSeconds: 86400, maxAgeCapDays: 30 },
search: { enabled: true, exclude: ['/404'] },
sitemap: {
  enabled: true,
  defaultPriority: 0.7,
  defaultChangeFreq: 'weekly',
},
robots: { enabled: true, sitemap: true },
rss: {
  enabled: true,
  feeds: [
    {
      filename: 'feed.xml',
      title: 'Stati Documentation Updates',
      description: 'Latest updates to Stati documentation',
      maxItems: 30,
    },
  ],
},

Start your first Stati site

The scaffolder asks a few questions, from CSS, Sass or Tailwind to TypeScript and git, then installs dependencies. Accept the defaults for a TypeScript project like the one above, three commands from a running site.

  1. npx create-stati my-site
  2. cd my-site
  3. npm run dev

The dev server runs on localhost:3000 by default and reloads the page every time you make changes.