Configuration (stati.config.ts)
Stati is designed to work great out of the box, but it’s also highly configurable. The stati.config.js file (or stati.config.ts for TypeScript projects) is where you customize every aspect of your site’s behavior, from basic metadata to advanced build optimizations.
Configuration File
Basic Setup
Create stati.config.js in your project root:
import { defineConfig } from '@stati/core';
export default defineConfig({
site: {
title: 'My Stati Site',
baseUrl: 'https://my-site.com',
defaultLocale: 'en-US',
},
});
TypeScript Configuration
For full type safety, use stati.config.ts:
import { defineConfig } from '@stati/core';
import type { StatiConfig } from '@stati/core';
const config: StatiConfig = {
site: {
title: 'My Stati Site',
baseUrl: 'https://my-site.com',
defaultLocale: 'en-US',
},
// Type-safe plugin configuration
markdown: {
plugins: [['footnote', { backref: true }]],
},
// ISG options with autocomplete
isg: {
enabled: true,
ttlSeconds: 21600,
},
};
export default defineConfig(config);
Configuration Sections
Site Metadata
The foundation of your site configuration:
export default defineConfig({
site: {
title: 'Stati Documentation', // Required
baseUrl: 'https://stati.build', // Required
description: 'Build static sites', // Optional
defaultLocale: 'en-US', // Optional
},
});
Required Fields:
title(string) - The site’s title, used in templates and metadatabaseUrl(string) - Base URL for the site, used for absolute URL generation
Optional Fields:
defaultLocale(string) - Default locale for internationalization (e.g., ‘en-US’, ‘fr-FR’)description(string) - Default site description for meta tags (used as fallback when page has no description)
Markdown Configuration
Configure markdown processing with plugins and custom renderer modifications:
export default defineConfig({
markdown: {
// Plugin configuration - array of plugin names or [name, options] tuples
plugins: [
'footnote', // markdown-it-footnote - Footnotes
['container', { validate: () => true }], // markdown-it-container with options
],
// Custom markdown-it configuration function
configure: (md) => {
// Add custom plugins or modify renderer
md.use(customPlugin, options);
// Modify rendering rules
md.renderer.rules.code_inline = (tokens, idx) => {
const token = tokens[idx];
return `<code class="inline-code">${token.content}</code>`;
};
},
},
});
Note: TOC extraction and heading anchor generation are built into Stati and enabled by default. You don’t need
markdown-it-anchorormarkdown-it-toc-done-rightplugins.
Available Options:
plugins(array) - Array of markdown-it plugin names (strings) or [name, options] tuplesconfigure(function) - Function that receives the markdown-it instance for custom configurationtoc(boolean) - Enable/disable TOC extraction and heading anchor generation (default:true)
Template Configuration
Configure the Eta template engine:
export default defineConfig({
eta: {
// Custom filters for Eta templates
filters: {
// Date formatting
date: (date) => {
return new Intl.DateTimeFormat('en-US', {
dateStyle: 'long',
}).format(new Date(date));
},
// Slugify text
slug: (text) => {
return String(text)
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
},
// Uppercase transform
upper: (text) => String(text).toUpperCase(),
},
},
});
Note: Filters are added to the
staticontext and called as functions.
Using filters in templates:
<!-- Apply date filter -->
<time><%= stati.date(stati.page.date) %></time>
<!-- Apply slug filter -->
<a href="/<%= stati.slug(stati.page.title) %>/">Link</a>
<!-- Apply uppercase filter -->
<span><%= stati.upper(stati.page.category) %></span>
<!-- Chain filters -->
<span><%= stati.upper(stati.slug(stati.page.title)) %></span>
ISG Configuration
Configure Incremental Static Generation for smart caching:
export default defineConfig({
isg: {
// Enable ISG caching
enabled: true,
// Default cache TTL in seconds
ttlSeconds: 21600, // 6 hours
// Maximum age cap in days
maxAgeCapDays: 365,
// Aging rules for progressive cache extension
aging: [
{ untilDays: 7, ttlSeconds: 86400 }, // 1 day for week-old content
{ untilDays: 30, ttlSeconds: 604800 }, // 1 week for month-old content
{ untilDays: 365, ttlSeconds: 2592000 }, // 30 days for year-old content
],
},
});
**Available Options:**
- `enabled` (boolean) - Enable or disable ISG caching (default: true)
- `ttlSeconds` (number) - Default cache time-to-live in seconds (default: 21600)
- `maxAgeCapDays` (number) - Maximum age in days for applying aging rules (default: 365)
- `aging` (array) - Array of aging rules with `untilDays` and `ttlSeconds` properties
**Aging Rules:**
Aging rules allow you to progressively extend cache TTL based on content age. Each rule specifies a time threshold (`untilDays`) and the cache duration (`ttlSeconds`) to apply to content that reaches that age.
```javascript
// Example: older content gets cached longer
aging: [
{ untilDays: 7, ttlSeconds: 86400 }, // 1 day cache for content 7+ days old
{ untilDays: 30, ttlSeconds: 604800 }, // 1 week cache for content 30+ days old
]
Development Server
Configure the development server:
export default defineConfig({
dev: {
// Server configuration
port: 3000, // Port for development server
host: 'localhost', // Host to bind to
open: false, // Whether to open browser automatically
},
});
Available Options:
port(number) - Port for development server (default: 3000)host(string) - Host to bind to (default: ‘localhost’)open(boolean) - Whether to open browser automatically (default: false)
Preview Server
Configure the preview server:
export default defineConfig({
preview: {
// Server configuration
port: 4000, // Port for preview server
host: 'localhost', // Host to bind to
open: false, // Whether to open browser automatically
},
});
Available Options:
port(number) - Port for preview server (default: 4000)host(string) - Host to bind to (default: ‘localhost’)open(boolean) - Whether to open browser automatically (default: false)
Note: CLI options (e.g.,
stati dev --port 8080orstati preview --port 8080) take precedence over config file settings.
Advanced Configuration
Build Hooks
Add custom logic at various stages of the build process:
export default defineConfig({
hooks: {
// Before build starts - receives BuildContext
async beforeAll(context) {
console.log('Starting build...');
console.log(`Building ${context.pages.length} pages`);
console.log(`Output directory: ${context.config.outDir}`);
},
// After build completes - receives BuildContext
async afterAll(context) {
console.log('Build completed!');
console.log(`Generated ${context.pages.length} pages`);
},
// Before each page renders - receives PageContext
async beforeRender(context) {
// Add computed properties to the page
context.page.frontMatter.readingTime = calculateReadingTime(context.page.content);
context.page.frontMatter.wordCount = countWords(context.page.content);
},
// After each page renders - receives PageContext
async afterRender(context) {
console.log(`Rendered: ${context.page.slug}`);
},
},
});
Available Hooks:
beforeAll(function) - Called before starting the build process, receivesBuildContextafterAll(function) - Called after completing the build process, receivesBuildContextbeforeRender(function) - Called before rendering each page, receivesPageContextafterRender(function) - Called after rendering each page, receivesPageContext
Context Types:
// BuildContext - passed to beforeAll and afterAll
interface BuildContext {
config: StatiConfig; // The resolved configuration
pages: PageModel[]; // Array of all loaded pages
}
// PageContext - passed to beforeRender and afterRender
interface PageContext {
page: PageModel; // The page being processed
config: StatiConfig; // The resolved configuration
}
Multiple Configuration Files
You can split configuration across multiple files:
// config/base.js
export const baseConfig = {
site: {
title: 'My Site',
baseUrl: 'https://example.com',
},
};
// stati.config.js
import { baseConfig } from './config/base.js';
export default defineConfig({
...baseConfig,
// Environment-specific overrides
site: {
...baseConfig.site,
baseUrl: process.env.SITE_URL || baseConfig.site.baseUrl,
},
});
Best Practices
Configuration Organization
-
Keep it readable
// Good: organized and commented export default defineConfig({ // Site metadata site: { title: 'My Site', baseUrl: 'https://example.com', }, // Markdown processing markdown: { plugins: ['footnote'], // markdown-it-footnote }, }); -
Use environment variables for secrets
export default defineConfig({ site: { title: 'My Site', baseUrl: process.env.SITE_URL || 'http://localhost:3000', }, }); -
Split large configurations
// config/site.js export const siteConfig = { title: 'My Site', baseUrl: 'https://example.com', }; // config/markdown.js export const markdownConfig = { plugins: ['footnote', 'emoji'], // Stati auto-prepends 'markdown-it-' }; // stati.config.js import { siteConfig } from './config/site.js'; import { markdownConfig } from './config/markdown.js'; export default defineConfig({ site: siteConfig, markdown: markdownConfig, });
Multi-Environment Configuration
Configure different settings for development, staging, and production:
// config/environments.js
const environments = {
development: {
site: {
title: 'My Site (Dev)',
baseUrl: 'http://localhost:3000',
},
isg: { enabled: false },
dev: { port: 3000, open: true },
},
staging: {
site: {
title: 'My Site (Staging)',
baseUrl: 'https://staging.mysite.com',
},
isg: { enabled: true, ttlSeconds: 300 },
},
production: {
site: {
title: 'My Site',
baseUrl: 'https://mysite.com',
},
isg: {
enabled: true,
ttlSeconds: 21600,
aging: [
{ untilDays: 7, ttlSeconds: 21600 },
{ untilDays: 30, ttlSeconds: 86400 },
],
},
},
};
export default environments;
// stati.config.js
import { defineConfig } from '@stati/core';
import environments from './config/environments.js';
const env = process.env.NODE_ENV || 'development';
const envConfig = environments[env] || environments.development;
export default defineConfig({
srcDir: 'site',
outDir: 'dist',
staticDir: 'public',
...envConfig,
});
Configuration Reference
For detailed information about specific configuration options, see:
- Site Metadata - Site title, base URL, and locale settings
- Template Settings - Eta template engine configuration
- Markdown Configuration - Plugins and markdown-it processing
- ISG Options - Incremental static generation and caching
- SEO Configuration - SEO metadata and optimization
- RSS Configuration - RSS feed generation
- TypeScript - Built-in TypeScript compilation with esbuild
The configuration system is designed to grow with your needs while maintaining simplicity for basic use cases. Start with minimal configuration and add complexity as your site evolves.