An unopinionated static site generator with a tiny api. Friendly with a watched hot reloaded environment.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Build Status npm

An unopinionated static site generator with a tiny api, that is friendly with a watched hot reloaded environment.


new SSG(opts = { dest: `${process.cwd()}/public` })

Creates a new SSG instance. opts.dest specifies the destination of the generated html files. opts.dest defaults to the public dir under the current working directory. It's probably easier to just use the next api instead.

ssg = require('tiny-ssg/instance')

Returns a new SSG instance with defaults args. Possible to set the destination by setting ssg.dest. This is more friendly with watch mode and invalidate-module.

ssg.manifest(cb: () => Promise<Page[]> | Page[]): void

Specifies the pages to be built.

Page has the type:

type Page = {
  url: string,
  view: (meta: any, page: Page) => Promise<string> | string,
  meta?: any,

url specifies the destination where you would ultimately access the page when served on your host. e.g. /, /posts/title-slug/, /posts/feed.xml.

view is a function whose return value will be written to disk at the appropriate file for the url. The return value may be a Promise that resolves into a string. It is passed the meta object and the Page object itself.

meta will be passed as the first argument to view. This could be anything that the view would find useful.

The cb arg to manifest should return a list or a Promise that resolves to a list of Page objects. Promise<void>

Executes the callback given in manifest() and builds the site according to the returned Page objects.

There is no guaranteed order of executing the view functions or the order of the writes. Views may be executed in parallel. For this reason, if your views depend on the same resource, you need to write code that is friendly with parallel access of resources. See the Recommended Utilities section.

If build() is ran again in the same process, it will rebuild the site with these differences:

  • if the view of a Page returns the same string, it will not write out
  • if a url is no longer returned since the previous manifest, the page will be deleted from the destination directory

trivial example

Here's a trivial example to demonstrate the API:

const ssg = require('tiny-ssg/instance');

ssg.manifest(() => {
  return [
    { url: '/', view: () => 'Hello World!' },

This will write to Hello World! to public/index.html.

watch mode example

tiny-ssg is great with watchers like chokidar and tools like invalidate-module:

// watch.js
const chokidar = require('chokidar');
const invalidate = require('invalidate-module');
const path = require('path');

const watcher ='*.js').on('all', (event, filename) => {
// build.js
const ssg = require('tiny-ssg/instance');

ssg.manifest(() => {
  return [
    { url: '/', view: () => 'Hello World!' },

module.exports = () =>;

Now you if you execute node watch.js, you may freely update the view function in build.js and it will rebuild the site.

markdown file example

Using marky-markdown, fs-extra, and async/await:

const fs = require('fs-extra');
const md = require('marky-markdown');
const ssg = require('tiny-ssg/instance');

ssg.manifest(() => {
  return [
    { url: '/', view: markdownView, meta: { file: '' } },

async function markdownView(meta) {
  const raw = await fs.readFile(meta.file);
  return md(raw);

react example

Use ReactDOMServer.renderToStaticMarkup:

const React = require('react');
const ssg = require('tiny-ssg/instance');
const { renderToStaticMarkup } = require('react-dom/server');

ssg.manifest(() => {
  return [
    { url: '/', view: reactView },

function reactView() {
  const element = (
      <head><title>Hello World!</title></head>
      <body><h1>Hello World!</h1></body>
  return `<!doctype html>${renderToStaticMarkup(element)}`

It's up to you on how you compose your React components. Stateless functional components work well here.

how to make rebuilds fast

In watch mode, since the build process is ran all over again on build(), you would need to prevent excessive calls to expensive operations for fast rebuilds. For example, if your view reads from a file, then the read should be cached and could be invalidated by the file's mtime.

Views that perform async I/O will be executed concurrently. If multiple views read from the same resource, you can use something like reuse-promise to prevent parallel reads of the same resource.