how I accidentally build a static page generator...

tobias hartmann . @ToH_82

I decided to build a private Homepage

a long long time ago


tobias hartmann . @ToH_82

How long does it take for a frontend dev to build a website?

It's compilcated

a lot of pain but I survived

the tech stack trap


tobias hartmann . @ToH_82

I realized, it's content

what really matters


tobias hartmann . @ToH_82

as simple as possible

as small as possible


tobias hartmann . @ToH_82


but all the cool stuff


tobias hartmann . @ToH_82

ookay, let's add some tech

I want a blog


tobias hartmann . @ToH_82

because it makes happy

break it down


tobias hartmann . @ToH_82

how small is small enough?




what is your experience?

tobias hartmann . @ToH_82

 handlebarsjs keeps it simple

warning: code


tobias hartmann . @ToH_82

partials for page parts

<!-- image within a post -->
<figure class="figure{{#if position}}  figure--break-{{position}}{{/if}}">
  <img class="figure__image" alt="{{title}}" 
       src="{{site.url.static}}assets/media/{{image}}" />
  <p class="figure__caption">{{caption}}</p>

<!-- article list item -->
<div class="card  js-hash-filter" data-tags="{{tags}}">
  <header class="card-header">
    <h2><a href="{{link}}">{{title}}</a> ({{lang}})</h2>
  <div class="card-content">
    <div class="inner">
      <a href="{{link}}" class="btn  btn-primary  btn-ghost"
         title="open article: {{title}} ({{lang}})">Open article</a>

helper for the list

module.exports = articleList

var glob = require('glob')
var fs = require('fs')
var cheerio = require('cheerio')
var getDocumentData = require('../document/getData')
var _ = require('underscore')

 * @private
 * @param {object} articles
 * @param {object} options
 * @returns {string}
function renderArticleList (articles, options) {
  if(articles.length === 0) {
    return ''

  articles = _.sortBy(articles, 'dateCode').reverse()

  var articlesHtml = ''
  articles.forEach(function (article) {
    articlesHtml = articlesHtml + options.fn(article)

  return articlesHtml

 * @private
 * @param {string} folder
 * @returns {Array}
function getArticlesFromFolder (folder) {
  var baseFolder = './src/'
  var files = glob.sync(baseFolder + folder + '/*.html')
  var articles = []

  files.forEach(function (file) {
    var content = fs.readFileSync(file, 'utf8')
    var $ = cheerio.load(content)

      title: $('header > h1').text(),
      author: $('header > .author').text(),
      intro: $('.main > p.intro').text(),
      link: file.replace(baseFolder, ''),
      lang: $('html').attr('lang'),
      date: $('time').text(),
      dateCode: $('time').attr('datetime'),
      tags: getDocumentData(content, 'tags')

  return articles

 * @public
 * @param {string} folder
 * @param {object} options
 * @returns {string}
function articleList (folder, options) {
  var articles = getArticlesFromFolder(folder)

  return renderArticleList(articles, options)

 but that sounds so complex...

a gulp module


tobias hartmann . @ToH_82

gulp module... easy

// trigger the data header reader
module.exports = loadData

var through = require('through2')
var extend = require('util')._extend
var getDocumentData = require('./getData')

function loadData () {
  return through.obj(function (file, enc, cb) {
    var fileContent = file.contents.toString()

    if (! {
      file['data'] = {}


    cb(null, file)

// read the data header
module.exports = getData

 * @param {string} content
 * @param {string} which
 * @returns {object}
function getData (content, which) {
  which = which || null

  var matches = content.match(/\|-.*.-\|/g)
  var docData = {}

  if (matches !== null) {
    matches.forEach(function (value) {
      var tmpData = value
        .replace('|-', '')
        .replace('-|', '')

      docData[tmpData[0]] = tmpData[1]

  if (which !== null) {
    return docData[which]

  return docData

// remove the data header
module.exports = removeData

var through = require('through2')

function removeData () {
  return through.obj(function (file, enc, cb) {
    var fileContent = file.contents.toString()

    fileContent = fileContent.replace(/\|-.*.-\|/g, '')
    if (file.isBuffer()) {
      file.contents = new Buffer(fileContent)

    cb(null, file)

how does the gulp look like

var gulp = require('gulp')

gulp.task('hbs', function () {
  var handlebars = require('gulp-compile-handlebars')
  var loadDocumentData = require('./script/document/loadData')
  var removeDocumentData = require('./script/document/removeData')
  var pageConfig = require('./src/config.json')

  var options = {
    ignorePartials: true,
    batch: ['./src/partials'],
    helpers: {
      formatTags: require('./script/helper/formatTags'),
      articleList: require('./script/helper/articleList'),
      tagNavigation: require('./script/helper/tagNavigation'),
      markdown: require('./script/helper/markdown')

  return gulp.src('./src/**/*.html')
    .pipe(handlebars(pageConfig, options))

gulp.task('js', function () {
  var uglify = require('gulp-uglify')
  var concat = require('gulp-concat')

  return gulp.src('src/js/*.js')

gulp.task('css', function () {
  var postcss = require('gulp-postcss')
  var sourcemaps = require('gulp-sourcemaps')
  var atImport = require('postcss-import')
  var cssnano = require('cssnano')

  return gulp.src('./src/css/styles.css')
        path: ['./node_modules', './src/css']

gulp.task('default', ['hbs', 'css', 'js'])
gulp.task('watch', function () {
  return'./src/*', ['hbs', 'css', 'js'])

fullpage template, markdown, easy

|-headline:headline test-|
|-subline:subline test-|
|-author:Tobias Hartmann-|

{{#> blogArticle}}# This is a Test with a full template
<p>you can put markdown or html for the content, have fun</p>
  <body class="hack  hack--toh">
    <div class="topbar">
      <div class="container">
        <div class="logo">
          <object type="image/svg+xml" data="">TOH</object>
        <nav class="menu  menu--horizontal">
          <a href="index.html" class="menu-item">» Index/About</a>
          <a href="blog.html" class="menu-item">» Blog</a>
    <div class="container">
        <h1>headline test <span class="subline">subline test</span></h1>
        <p class="author">von Tobias Hartmann</p>
        <time datetime="2017-01-01">01 Januar 2017</time>
        <p><a target="_blank" href=" test - by ">Share this article on twitter</a>
    <main class="main container">
      <h1>This is a Test with a full template</h1>
<p>you can put markdown or html for the content</p>

make it better, iteratively

prototype first


tobias hartmann . @ToH_82

what, if you prototype first?

- faster development

- faster decisions

- more flexibility

- less code for the trashbin

- better estimations

tobias hartmann . @ToH_82

tobias hartmann . @ToH_82

tobias hartmann . @ToH_82

why is refactoring a good thing?

- code quality

- structure

- easy updates

- remember

tobias hartmann . @ToH_82

key to refactoring and iterative work

- transparency

- don't touch everything at once

- measure it

- trust

- focus

if a paper isn't enough anymore

ideas everywhere


tobias hartmann . @ToH_82

tobias hartmann . @ToH_82

realize your great work and party hard

uh, what's this


tobias hartmann . @ToH_82

keep it simple,

keep it small,

feel great,

make it better,

make it awesome


tobias hartmann

How I accidentally build a static page generator

By Tobias Hartmann

How I accidentally build a static page generator

In this talk, I'll walk through how I build my own website and blog out of handlebarsjs, with a little help of gulp and node. I'll share my experience about how hard but simple it can be, to build up a personal page and blog. This experience includes thoughts about iterative work, prototype first and splitting of tasks.

  • 3,752