npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stati/core

v1.23.2

Published

**The core engine powering Stati - a minimal, TypeScript-first static site generator that's fast to learn and even faster to build with.**

Readme

@stati/core

The core engine powering Stati - a minimal, TypeScript-first static site generator that's fast to learn and even faster to build with.

Built for developers who want modern tooling without the complexity. Write in Markdown, template with Eta, and deploy anywhere.


Getting Started

The Easy Way (Recommended)

If you're new to Stati, start with our scaffolding tool:

npx create-stati my-site

cd my-site
npm run dev

This creates a complete Stati project with sensible defaults, your choice of CSS framework, and everything configured for you.

Using the Core Package Directly

For advanced users who want programmatic control or to integrate Stati into existing tooling:

npm install @stati/core

Quick Example

Here's how simple it is to build a static site with Stati:

1. Create a configuration file (stati.config.js):

import { defineConfig } from '@stati/core';

export default defineConfig({
  site: {
    title: 'My Site',
    baseUrl: 'https://example.com',
  },
});

2. Add some content (site/index.md):

---
title: Welcome
---

# Hello Stati

This is my first page. It's just Markdown.

3. Build or develop:

import { build, createDevServer } from '@stati/core';

// Development with live reload
const server = await createDevServer({
  port: 3000,
  open: true,
});

// Or build for production
const stats = await build({
  clean: true,
});

That's it! Your site is ready.


Why Choose @stati/core?

For New Users

  • Zero Configuration Required - Works out of the box with sensible defaults
  • Simple File Structure - Markdown files automatically become pages
  • Live Reload Built-In - See changes instantly during development
  • SEO Ready - Automatic meta tags and structured data

For Advanced Users

  • Programmatic API - Full control over the build process
  • TypeScript-First - Complete type safety for configuration
  • TypeScript Compilation - Built-in esbuild for client-side TypeScript
  • Extensible Hooks - Customize every stage of the build
  • Smart Caching - Incremental builds with intelligent invalidation

Core Concepts

How Stati Works

Stati uses a simple, file-based approach:

my-site/
├── site/           # Your content (Markdown + templates)
│   ├── index.md    # Homepage → /
│   └── blog/
│       └── hello.md # Blog post → /blog/hello/
├── public/         # Static assets (CSS, images)
└── stati.config.js # Configuration (optional)

The workflow:

  1. Write content in Markdown with front-matter
  2. Stati processes it through templates
  3. Static HTML is generated with smart caching
  4. Deploy anywhere (Netlify, Vercel, GitHub Pages, etc.)

Key Features

Markdown-First Content

Write naturally with front-matter for metadata:

---
title: My Post
description: A great article
date: 2024-01-15
tags: [tutorial, stati]
---

# My Post

Your content here...

Flexible Templating

Customize layouts with Eta templates:

<!DOCTYPE html>
<html>
  <head>
    <title><%= stati.page.title %></title>
  </head>
  <body>
    <%~ stati.content %>
  </body>
</html>

Incremental Static Generation (ISG)

Only rebuild what changed:

  • Smart caching based on file modification times
  • TTL-based refresh for dynamic content
  • Tag-based invalidation for related content
import { invalidate } from '@stati/core';

// Invalidate by tag
await invalidate('tag:blog');

// Invalidate by path
await invalidate('path:/posts');

// Invalidate old content (3+ months)
await invalidate('age:3months');

Built-In SEO

SEO optimization works automatically with zero configuration:

  • Meta tags (title, description, keywords)
  • Open Graph for social sharing
  • Twitter Cards
  • Structured data (JSON-LD)

Enable sitemap and robots.txt with one config option.


Complete Configuration Reference

Stati works with zero configuration, but you can customize every aspect when needed.

Minimal Setup (Recommended for Beginners)

import { defineConfig } from '@stati/core';

export default defineConfig({
  site: {
    title: 'My Stati Site',
    baseUrl: 'https://example.com',
  },
});

This is all you need! Stati automatically enables:

  • ISG caching with 6-hour TTL
  • SEO auto-injection for all pages
  • Markdown processing with standard features

Extended Configuration (Common Options)

Below are commonly used configuration options. This is not a complete list - see the full configuration documentation for all available options.

import { defineConfig } from '@stati/core';

export default defineConfig({
  // Directory configuration
  srcDir: 'site',           // Content source (default: 'site')
  outDir: 'dist',           // Build output (default: 'dist')
  staticDir: 'public',      // Static assets (default: 'public')

  // Site metadata
  site: {
    title: 'My Stati Site',
    baseUrl: 'https://example.com',
    defaultLocale: 'en-US',
  },

  // Markdown processing
  markdown: {
    toc: true,                                      // TOC + heading anchors (default: true)
    plugins: [
      ['external-links', { externalTarget: '_blank' }], // External links in new tab
    ],
    configure: (md) => {
      // Configure MarkdownIt instance directly
      md.set({
        html: true,
        linkify: true,
        typographer: true,
      });
    },
  },

  // Eta template engine
  eta: {
    filters: {
      formatDate: (date) => new Date(date).toLocaleDateString('en-US'),
      slugify: (text) => text.toLowerCase().replace(/\s+/g, '-'),
    },
  },

  // Incremental Static Generation (enabled by default)
  isg: {
    ttlSeconds: 86400,        // Cache TTL: 24 hours (default: 21600 / 6 hours)
    maxAgeCapDays: 30,        // Max age for aging rules (default: 365)
    aging: [
      { untilDays: 7, ttlSeconds: 86400 },   // 1 day cache for week-old content
      { untilDays: 30, ttlSeconds: 604800 }, // 1 week cache for month-old content
    ],
  },

  // SEO configuration (auto-injection enabled by default)
  seo: {
    defaultAuthor: {
      name: 'John Doe',
      email: '[email protected]',
      url: 'https://johndoe.com',
    },
    debug: false,             // Enable SEO debug logging
  },

  // Sitemap generation (opt-in)
  sitemap: {
    enabled: true,            // Generate sitemap.xml
    defaultPriority: 0.5,     // Default priority for pages
    defaultChangeFreq: 'monthly',
    excludePatterns: ['/draft/**', '/admin/**'], // Exclude patterns
    priorityRules: [
      { pattern: '/', priority: 1.0 },           // Homepage highest priority
      { pattern: '/blog/**', priority: 0.8 },    // Blog posts high priority
      { pattern: '/docs/**', priority: 0.7 },    // Documentation
    ],
  },

  // Robots.txt generation (opt-in)
  robots: {
    enabled: true,            // Generate robots.txt
    disallow: ['/admin/', '/draft/', '/private/'], // Paths to block
    crawlDelay: 10,           // Crawl delay in seconds
    sitemap: true,            // Auto-include sitemap URL
    customLines: [
      '# Custom directives',
      'User-agent: GPTBot',
      'Disallow: /',
    ],
  },

  // Development server
  dev: {
    port: 3000,
    host: 'localhost',
    open: false,
  },

  // Preview server
  preview: {
    port: 4000,
    host: 'localhost',
    open: false,
  },

  // Build lifecycle hooks
  hooks: {
    beforeAll: async (ctx) => {
      console.log(`Building ${ctx.pages.length} pages`);
    },
    beforeRender: async (ctx) => {
      // Custom pre-render logic
    },
    afterRender: async (ctx) => {
      // Custom post-render logic
    },
  },
});

For the complete configuration reference including all options, advanced features, and detailed explanations, see the Configuration Guide.


API Reference

Build Functions

build(options?: BuildOptions): Promise<BuildStats>

Build your static site for production.

Options:

{
  force?: boolean;        // Force rebuild of all pages (ignores cache)
  clean?: boolean;        // Clean output directory before build
  includeDrafts?: boolean; // Include draft pages in build
  configPath?: string;    // Custom config file path
}

Example:

import { build } from '@stati/core';

const stats = await build({
  clean: true,
  force: false,
  includeDrafts: false,
});

console.log(`Built ${stats.totalPages} pages in ${stats.buildTimeMs}ms`);

Returns: Build statistics including pages built, duration, and cache hits.


createDevServer(options?: DevServerOptions): Promise<DevServer>

Start a development server with live reload.

Options:

{
  port?: number;          // Port number (default: 3000)
  host?: string;          // Host address (default: 'localhost')
  open?: boolean;         // Auto-open browser (default: false)
  configPath?: string;    // Custom config file path
}

Example:

import { createDevServer } from '@stati/core';

const server = await createDevServer({
  port: 3000,
  open: true,
});

console.log(`Dev server running at http://localhost:3000`);

Returns: Server instance with methods to stop and restart.


createPreviewServer(options?: PreviewServerOptions): Promise<PreviewServer>

Start a production preview server to test your built site locally.

Options:

{
  port?: number;          // Port number (default: 4000)
  host?: string;          // Host address (default: 'localhost')
  open?: boolean;         // Auto-open browser (default: false)
  configPath?: string;    // Custom config file path
}

Example:

import { createPreviewServer } from '@stati/core';

// First build your site
await build({ clean: true });

// Then preview it
const server = await createPreviewServer({
  port: 4000,
  open: true,
});

console.log(`Preview server running at http://localhost:4000`);

Returns: Server instance with methods to stop.

Note: Unlike createDevServer, the preview server serves the static files from your dist/ directory without live reload or rebuilding. This is useful for testing your production build locally before deployment.


invalidate(query?: string): Promise<InvalidationResult>

Invalidate cache by tags, paths, patterns, or age.

Query Syntax:

  • tag:blog - Invalidate pages with specific tag
  • path:/posts - Invalidate pages under path
  • glob:/blog/** - Invalidate by glob pattern
  • age:3months - Invalidate content younger than 3 months
  • No query - Clear entire cache

Examples:

import { invalidate } from '@stati/core';

// Invalidate by tag
await invalidate('tag:news');

// Invalidate by path prefix
await invalidate('path:/blog/2024/');

// Invalidate old content
await invalidate('age:6months');

// Multiple criteria (OR logic)
await invalidate('tag:blog age:1month');

// Clear everything
await invalidate();

Returns: Invalidation result with count of pages affected.


defineConfig(config: StatiConfig): StatiConfig

Define a type-safe configuration with full TypeScript support.

Example:

import { defineConfig } from '@stati/core';

export default defineConfig({
  site: {
    title: 'My Site',
    baseUrl: 'https://example.com',
  },
  // TypeScript provides autocomplete and validation
});

loadConfig(cwd?: string): Promise<StatiConfig>

Load and validate Stati configuration from a project directory.

Example:

import { loadConfig } from '@stati/core';

// Load from current directory
const config = await loadConfig();

// Load from specific directory
const config2 = await loadConfig('/path/to/project');

TypeScript Types

Full type definitions for TypeScript users:

import type {
  // Configuration
  StatiConfig,
  ISGConfig,
  BuildHooks,

  // Build
  BuildOptions,
  BuildContext,
  BuildStats,

  // Pages
  PageModel,
  PageContext,
  FrontMatter,

  // Navigation
  NavNode,

  // Development
  DevServerOptions,
  PreviewServerOptions,

  // Cache
  InvalidationResult,
  AgingRule,
} from '@stati/core/types';

Need more types? This is a curated list of commonly used types. For the complete type reference including all SEO, sitemap, and configuration types, see the API Types Documentation.


Features Deep Dive

Markdown Processing

Stati supports rich Markdown features out of the box:

  • Front-matter - YAML, TOML, or JSON metadata
  • Plugins - Full markdown-it ecosystem compatibility
  • Syntax highlighting - Code blocks with language support
  • Custom rendering - Configure parser behavior
  • Draft mode - Mark pages as draft: true

Example with plugins:

export default defineConfig({
  markdown: {
    toc: true,                     // TOC + heading anchors (built-in, default: true)
    plugins: [
      'footnote',                  // Footnote support
      ['abbr', { /* options */ }], // Abbreviations
    ],
  },
});

Template Engine

Powered by Eta for fast, flexible templates:

  • Layouts - Template inheritance via layout property
  • Partials - Reusable components
  • Custom filters - Transform data in templates
  • Type-safe helpers - Access page data with autocomplete
  • Hot reload - See template changes instantly

Template structure:

<!-- site/layout.eta -->
<!DOCTYPE html>
<html>
  <head>
    <title><%= stati.page.title %> - <%= stati.site.title %></title>
  </head>
  <body>
    <%~ include('_partials/header') %>
    <main>
      <%~ stati.content %>
    </main>
    <%~ include('_partials/footer') %>
  </body>
</html>

Navigation System

Automatic navigation hierarchy based on your file structure:

  • Auto-generated tree - Reflects directory structure
  • Breadcrumbs - Parent-child relationships
  • Custom ordering - Use order in front-matter
  • Index pages - Special handling for index.md

Access in templates:

<!-- Show breadcrumbs -->
<nav>
  <% stati.nav.getBreadcrumbs().forEach(crumb => { %>
    <a href="<%= crumb.url %>"><%= crumb.title %></a>
  <% }) %>
</nav>

<!-- Show navigation tree -->
<ul>
  <% stati.nav.tree.forEach(item => { %>
    <li><a href="<%= item.url %>"><%= item.title %></a></li>
  <% }) %>
</ul>

Development Experience

Built for productivity:

  • Live reload - WebSocket-based instant updates
  • Fast rebuilds - Only rebuild changed pages
  • Error overlay - See build errors in browser
  • Static assets - Served from public/ directory
  • Source maps - Debug with original code

Caching & Performance

Smart caching for lightning-fast builds:

  • Modification tracking - Rebuild only changed files
  • Incremental builds - Skip unchanged pages
  • Tag invalidation - Update related content together
  • TTL-based refresh - Control cache lifetime
  • Age-based rules - Different TTL for old content

Cache strategies:

export default defineConfig({
  isg: {
    ttlSeconds: 21600,     // 6 hours default
    aging: [
      { untilDays: 7, ttlSeconds: 3600 },    // Recent: 1 hour
      { untilDays: 30, ttlSeconds: 86400 },  // Month: 1 day
      // Older content uses default TTL
    ],
  },
});

Use Cases

Documentation Sites

Perfect for technical documentation:

export default defineConfig({
  site: {
    title: 'My Project Docs',
    baseUrl: 'https://docs.example.com',
  },
  markdown: {
    // TOC and heading anchors are built-in (enabled by default)
    plugins: ['container', 'footnote'],
  },
});

Blogs

Great for content-heavy sites:

export default defineConfig({
  site: {
    title: 'My Blog',
    baseUrl: 'https://blog.example.com',
  },
  isg: {
    aging: [
      { untilDays: 30, ttlSeconds: 3600 },   // Fresh posts: 1 hour
      { untilDays: 365, ttlSeconds: 86400 }, // Year-old: 1 day
    ],
  },
});

Landing Pages

Fast, SEO-optimized marketing sites:

export default defineConfig({
  site: {
    title: 'Product Name',
    baseUrl: 'https://example.com',
  },
  seo: {
    defaultAuthor: {
      name: 'Company Name',
      url: 'https://example.com',
    },
  },
});

Requirements

  • Node.js >=22
  • npm 11.5.1 or higher (or equivalent package manager)

Learn More


Philosophy

Stati Core is built on these principles:

  • Simplicity First - Sensible defaults that just work
  • Performance Matters - Fast builds, smart caching
  • Developer Experience - Great tooling, clear errors
  • Type Safety - TypeScript throughout
  • Extensibility - Customize when you need to

Support & Community


License

MIT © Imre Csige


Ready to build?

# New project (easiest way)
npx create-stati my-site

# Or use core directly
npm install @stati/core