Write You a Static Site Generator
With NodeJS ⚡️
Adam Kelly
TY Student at Skerries Community College
@adamisntdead
tinyurl.com/node-static
Have A Question? 🤔
What is a Static Site Generator?
Why Would You Use a Static Site Generator?
- Simplicity
- Security
- Performance
- Flexibility

Writing a Static Site Generator ✏️
Why Would You Write Your Own Static Site Generator?
- You choose your stack
- Speed
- An in-depth knowledge of the internals
- Easier to debug and solve problems
- Reduce reliance on external/upstream projects
Why Would You Use NodeJS
- Ecosystem
- Fast
- Async
- Isomorphism
Two Big Questions
What Does Your Content Look Like
How Are You Going To Edit Your Content
Smaller Choices
Picking Your Tools
Configuration
JSON

YAML

Content / Layouts
Markdown

Handlebars

Pug

Generic Tools
fs-extra

fast-glob

gray-matter

4 Main Parts Of A Static Site Generator
- Cleaning
- Reading
- Rendering
- Writing
The Code
My Choices
- JSON for configuration
- Markdown for content
- YAML for frontmatter
- Handlebars for layouts
Configuration
{
"title": "Your Awesome title",
"description": "Write an awesome description here!",
"author": "Author Name",
"static": "assets",
"public": "public"
}const fs = require('fs-extra')
async function getConfig() {
// Reading the 'config.json' file,
// and parsing the JSON
return await fs.readJSON('config.json')
}The Main Generator
class Site {
constructor(config) {
this.config = config
this.pages = []
this.output = []
}
// Main Method - Builds the site
build() {
...
}
...
}getConfig()
.then(config => new Site(config))
.then(site => site.build())
.catch(err => console.log(err))
The 4 Main Stages
class Site {
...
async build() {
await this.reset()
await this.read()
await this.render()
await this.write()
}
async reset() {
...
}
async read() {
...
}
async render() {
...
}
async write() {
...
}
}async reset() {
// Delete the public / output directory
await fs.remove(this.config.public)
// Create a new, empty public directory
await fs.mkdirp(this.config.public)
}const matter = require('gray-matter')
const slug = require('slug')
const glob = require('fast-glob')
...
async read() {
// Read the files in the content directory
const files = await glob('**/*', { cwd: 'content' })
for (const filename of files) {
const fileContent = await fs.readFile(`content/${filename}`, 'utf8')
// Getting the frontmatter
const { content, data } = matter(fileContent)
const title = data.title
const permalink = slug(title).toLowerCase()
// Add the page to the 'this.pages' object
this.pages.push({
title,
permalink,
content,
...frontmatter.data
})
}
}
async render() {
const site = { pages: this.pages, ...this.config }
for (const page in this.pages) {
let content = Handlebars.compile(page.content)({ site, page })
if (page.markdown) {
content = marked(renderedContent)
}
if (page.layout) {
const layout = await fs.readFile(`layouts/${page.layout}.hbs`, 'utf8')
const template = Handlebars.compile(layout)
content = template({ site, page, content })
}
this.output.push({
path: path.join(this.config.public, page.permalink, 'index.html'),
content
})
}
}async write() {
const outputs = []
for (const output in this.output) {
outputs.push(fs.outputFile(output.path, output.content))
}
return Promise.all(outputs)
}Thats It!
Extending Your Generator
- Asset Pipelines
- CLI
- Watching For Changes (Incremental Builds)
- Plugins
J
Javascript
APIs
A
Markup
M
Best Practices
What Makes The JAM Stack Special
Best Practices
- Everything On A CDN
- Atomic Deploys
- Instant Cache Invalidation
- Version Control Everything
- Automated Builds
Node And The JAM Stack
JAM Tools
E-Commerce

Gumroad
Shopify Buy Button

Stripe

GoCommerce
Forms
Formspree

Typeform

Netlify Forms

Search
Algolia

Google Custom Search

Front End Javascript Search

Login
Auth0

Netlify Identity

Comments
Disqus

Facebook Comments

Other Options

CMS
Contentful

Prismic

Storyblok

Dropbox

Netlify CMS

When Would I Not JAM?
Thank You!
@adamisntdead
tinyurl.com/node-static
Write You A Static Site Generator
By Adam Kelly
Write You A Static Site Generator
- 1,089