Configuration
Stati is designed to work great out of the box, but it’s also highly configurable. The stati.config.js file 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',
},
// Full type safety for all options
markdown: {
options: {
html: true,
linkify: true,
typographer: true,
},
},
};
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.dev', // Required
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’)
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: [
'anchor', // markdown-it-anchor - Add anchors to headings
'toc-done-right', // markdown-it-toc-done-right - Table of contents
['footnote', { /* options */ }], // markdown-it-footnote - Footnotes 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>`;
};
},
},
});
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 configuration
Template Configuration
Configure the Eta template engine:
export default defineConfig({
eta: {
// Custom filters for Eta templates
filters: {
// Date formatting
date: (date, format = 'long') => {
return new Intl.DateTimeFormat('en-US', {
dateStyle: format,
}).format(new Date(date));
},
// Slugify text
slug: (text) => {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
},
// Truncate text
truncate: (text, length = 150) => {
if (text.length <= length) return text;
return text.slice(0, length) + '...';
},
},
},
});
ISG Configuration
Configure Incremental Static Generation for smart caching:
export default defineConfig({
isg: {
// Enable ISG caching
enabled: true,
// Default cache TTL in seconds
ttlSeconds: 3600, // 1 hour
// Maximum age cap in days
maxAgeCapDays: 30,
// 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: false)ttlSeconds(number) - Default cache time-to-live in seconds (default: 3600)maxAgeCapDays(number) - Maximum age in days for applying aging rulesaging(array) - Array of aging rules withuntilDaysandttlSecondsproperties
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.
// 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
},
});
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
},
});
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
}
Environment-based Configuration
You can use environment variables to configure your site differently for development, staging, and production:
// stati.config.js
const isDev = process.env.NODE_ENV === 'development';
const isProd = process.env.NODE_ENV === 'production';
export default defineConfig({
site: {
title: 'My Site',
baseUrl: isProd ? 'https://my-site.com' : 'http://localhost:3000',
},
// Enable ISG only in production
isg: {
enabled: isProd,
ttlSeconds: isProd ? 3600 : 0,
},
// Development server configuration
dev: {
port: parseInt(process.env.PORT || '3000'),
open: isDev,
},
});
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: ['anchor'], // markdown-it-anchor }, }); -
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: ['anchor', 'toc-done-right'], // 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: 3600,
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
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.