diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..90500ef
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,2 @@
+use nix
+eval "$(lorri direnv)"
diff --git a/.ghci b/.ghci
new file mode 100644
index 0000000..992189d
--- /dev/null
+++ b/.ghci
@@ -0,0 +1,4 @@
+:def hoogle \x -> return $ ":!hoogle --count=15 \"" ++ x ++ "\""
+:def doc \x -> return $ ":!hoogle --info \"" ++ x ++ "\""
+:set -Wall
+:set -fno-warn-type-defaults
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..ba79d38
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+github: rpearce
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..1f97b0b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,13 @@
+version: 2
+updates:
+
+- package-ecosystem: github-actions
+  directory: "/"
+  schedule:
+    interval: daily
+    time: '00:00'
+    timezone: UTC
+  open-pull-requests-limit: 10
+  commit-message:
+      prefix: "chore"
+      include: "scope"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..5802c51
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,55 @@
+name: CI
+
+on:
+  pull_request:
+  push:
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Install Nix
+      uses: cachix/install-nix-action@v11
+      with:
+        skip_adding_nixpkgs_channel: true
+
+    - name: Build with cachix
+      uses: cachix/cachix-action@v6
+      with:
+        name: my-site
+        signingKey: ${{ secrets.CACHIX_SIGNING_KEY }}
+        #authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
+
+    - run: nix-build
+    - run: nix-shell --run "echo OK"
+
+    - name: Artifact pages
+      uses: actions/upload-artifact@v2
+      with:
+        name: pages
+        path: result/dist
+
+  deploy:
+    if: github.ref == 'refs/heads/master'
+    runs-on: ubuntu-latest
+    needs: [build]
+
+    steps:
+      - name: Download artifact
+        uses: actions/download-artifact@v2
+        with:
+          name: pages
+          path: result
+
+      - name: GitHub Pages
+        if: success()
+        uses: crazy-max/ghaction-github-pages@v2
+        with:
+          build_dir: result
+          target_branch: gh-pages
+          keep_history: false
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e6d56f2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.ghc.environment.*
+.pre-commit-config.yaml
+dist
+dist-newstyle
+hakyll-cache
+node_modules
+result
diff --git a/README.md b/README.md
index 6224274..396a24c 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,6 @@
-# hakyll-starter
-Hakyll starter template
+# hakyll-nix-starter
+
+[![built with nix](https://builtwithnix.org/badge.svg)](https://builtwithnix.org)
+
+[Hakyll](https://jaspervdj.be/hakyll/) + [Nix](https://nixos.org/) starter
+template
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..dda5d72
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,26 @@
+let
+  cfg = import ./nix/default.nix {};
+in
+{ pkgs ? cfg.pkgs }:
+
+  pkgs.stdenv.mkDerivation {
+    name = "my-site";
+    buildInputs = [
+      cfg.generator
+    ];
+    src = cfg.src;
+
+    # https://github.com/jaspervdj/hakyll/issues/614
+    # https://github.com/NixOS/nix/issues/318#issuecomment-52986702
+    # https://github.com/MaxDaten/brutal-recipes/blob/source/default.nix#L24
+    LOCALE_ARCHIVE = pkgs.lib.optionalString (pkgs.buildPlatform.libc == "glibc") "${pkgs.glibcLocales}/lib/locale/locale-archive";
+    LANG = "en_US.UTF-8";
+
+    buildPhase = ''
+      hakyll-site build
+    '';
+    installPhase = ''
+      mkdir -p "$out/dist"
+      cp -r ../dist/* "$out/dist"
+    '';
+  }
diff --git a/generator/LICENSE b/generator/LICENSE
new file mode 100644
index 0000000..56fa0c3
--- /dev/null
+++ b/generator/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2019, Robert Pearce
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Robert Pearce nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/generator/default.nix b/generator/default.nix
new file mode 100644
index 0000000..f61076f
--- /dev/null
+++ b/generator/default.nix
@@ -0,0 +1,3 @@
+{ pkgs }:
+
+(pkgs.callPackage ./hpkgs.nix {}).my-site
diff --git a/generator/hakyll.patch b/generator/hakyll.patch
new file mode 100644
index 0000000..e69de29
diff --git a/generator/hpkgs.nix b/generator/hpkgs.nix
new file mode 100644
index 0000000..ee1f778
--- /dev/null
+++ b/generator/hpkgs.nix
@@ -0,0 +1,31 @@
+{ compiler ? "ghc884"
+, pkgs
+}:
+
+let
+  inherit (pkgs.lib.trivial) flip pipe;
+  inherit (pkgs.haskell.lib) appendPatch appendConfigureFlags dontCheck;
+
+  hakyllFlags = [ "-f" "watchServer" "-f" "previewServer" ];
+
+  haskellPackages = pkgs.haskell.packages.${compiler}.override {
+    overrides = hpNew: hpOld: {
+      hakyll =
+        pipe
+          hpOld.hakyll
+          [
+            (flip appendPatch ./hakyll.patch)
+            (flip appendConfigureFlags hakyllFlags)
+          ];
+
+      my-site = hpNew.callCabal2nix "my-site" ./. {};
+
+      # because hakyll is marked as broken in nixpkgs
+      hslua = dontCheck (hpNew.callHackage "hslua" "1.0.3.2" {});
+      jira-wiki-markup = dontCheck (hpNew.callHackage "jira-wiki-markup" "1.1.4" {});
+      pandoc = dontCheck (hpNew.callHackage "pandoc" "2.9.2.1" {});
+      pandoc-types = dontCheck (hpNew.callHackage "pandoc-types" "1.20" {});
+    };
+  };
+in
+haskellPackages
diff --git a/generator/my-site.cabal b/generator/my-site.cabal
new file mode 100644
index 0000000..43710d2
--- /dev/null
+++ b/generator/my-site.cabal
@@ -0,0 +1,19 @@
+cabal-version:       2.4
+
+name:                my-site
+version:             0.1.0.0
+build-type:          Simple
+license:             BSD-3-Clause
+license-file:        LICENSE
+
+executable hakyll-site
+  main-is:           Main.hs
+  hs-source-dirs:    src
+  build-depends:     base == 4.*
+                   , hakyll ^>= 4.13.3.0
+                   , pandoc >= 2.0.5 && < 2.10
+                   , text ^>= 1.2.4
+                   , time >= 1.8 && < 1.10
+  other-modules:     Slug
+  ghc-options:       -Wall -threaded
+  default-language:  Haskell2010
diff --git a/generator/shell.nix b/generator/shell.nix
new file mode 100644
index 0000000..4c8c288
--- /dev/null
+++ b/generator/shell.nix
@@ -0,0 +1,21 @@
+let
+  cfg = import ../nix/default.nix {};
+  hp = cfg.haskellPackages;
+in
+{}:
+
+  hp.shellFor {
+    packages = p: [
+      p.my-site
+    ];
+
+    buildInputs = with hp; [
+      cabal-install
+      ghcid
+      hlint
+      hp.my-site
+      ormolu
+    ];
+
+    withHoogle = true;
+  }
diff --git a/generator/src/Main.hs b/generator/src/Main.hs
new file mode 100644
index 0000000..f9f4ddf
--- /dev/null
+++ b/generator/src/Main.hs
@@ -0,0 +1,201 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import Control.Monad (forM_)
+import Data.Maybe (fromMaybe)
+import qualified Data.Text as T
+import Hakyll
+import Slug (toSlug)
+import Text.Pandoc
+  ( Extension (Ext_auto_identifiers, Ext_fenced_code_attributes, Ext_footnotes, Ext_smart),
+    Extensions,
+    ReaderOptions,
+    WriterOptions,
+    extensionsFromList,
+    githubMarkdownExtensions,
+    readerExtensions,
+    writerExtensions,
+  )
+
+-- CONFIG
+
+root :: String
+root =
+  "https://mywebsite.com"
+
+siteName :: String
+siteName =
+  "My Site Name"
+
+config :: Configuration
+config =
+  defaultConfiguration
+    { destinationDirectory = "../dist",
+      ignoreFile = const False,
+      previewHost = "127.0.0.1",
+      previewPort = 8000,
+      providerDirectory = "../src",
+      storeDirectory = "../hakyll-cache",
+      tmpDirectory = "../hakyll-cache/tmp"
+    }
+
+-- BUILD
+
+main :: IO ()
+main = hakyllWith config $ do
+  forM_
+    [ "CNAME",
+      "favicon.ico",
+      "robots.txt",
+      "_config.yml",
+      "images/*",
+      "js/*",
+      "fonts/*"
+    ]
+    $ \f -> match f $ do
+      route idRoute
+      compile copyFileCompiler
+  match "css/*" $ do
+    route idRoute
+    compile compressCssCompiler
+  match "posts/*" $ do
+    let ctx = constField "type" "article" <> postCtx
+    route $ metadataRoute titleRoute
+    compile $
+      pandocCompilerCustom
+        >>= loadAndApplyTemplate "templates/post.html" ctx
+        >>= saveSnapshot "content"
+        >>= loadAndApplyTemplate "templates/default.html" ctx
+  match "index.html" $ do
+    route idRoute
+    compile $ do
+      posts <- recentFirst =<< loadAll "posts/*"
+      let indexCtx =
+            listField "posts" postCtx (return posts)
+              <> constField "root" root
+              <> constField "siteName" siteName
+              <> defaultContext
+      getResourceBody
+        >>= applyAsTemplate indexCtx
+        >>= loadAndApplyTemplate "templates/default.html" indexCtx
+  match "templates/*" $ compile templateBodyCompiler
+  create ["sitemap.xml"] $ do
+    route idRoute
+    compile $ do
+      posts <- recentFirst =<< loadAll "posts/*"
+      nzPages <- loadAll "new-zealand/**"
+      let pages = posts <> nzPages
+          sitemapCtx =
+            constField "root" root
+              <> constField "siteName" siteName
+              <> listField "pages" postCtx (return pages)
+      makeItem ("" :: String)
+        >>= loadAndApplyTemplate "templates/sitemap.xml" sitemapCtx
+  create ["rss.xml"] $ do
+    route idRoute
+    compile (feedCompiler renderRss)
+  create ["atom.xml"] $ do
+    route idRoute
+    compile (feedCompiler renderAtom)
+
+-- CONTEXT
+
+feedCtx :: Context String
+feedCtx =
+  titleCtx
+    <> postCtx
+    <> bodyField "description"
+
+postCtx :: Context String
+postCtx =
+  constField "root" root
+    <> constField "siteName" siteName
+    <> dateField "date" "%Y-%m-%d"
+    <> defaultContext
+
+titleCtx :: Context String
+titleCtx =
+  field "title" updatedTitle
+
+-- TITLE HELPERS
+
+replaceAmp :: String -> String
+replaceAmp =
+  replaceAll "&" (const "&amp;")
+
+replaceTitleAmp :: Metadata -> String
+replaceTitleAmp =
+  replaceAmp . safeTitle
+
+safeTitle :: Metadata -> String
+safeTitle =
+  fromMaybe "no title" . lookupString "title"
+
+updatedTitle :: Item a -> Compiler String
+updatedTitle =
+  fmap replaceTitleAmp . getMetadata . itemIdentifier
+
+-- PANDOC
+
+pandocCompilerCustom :: Compiler (Item String)
+pandocCompilerCustom =
+  pandocCompilerWith pandocReaderOpts pandocWriterOpts
+
+pandocExtensionsCustom :: Extensions
+pandocExtensionsCustom =
+  githubMarkdownExtensions
+    <> extensionsFromList
+      [ Ext_auto_identifiers,
+        Ext_fenced_code_attributes,
+        Ext_smart,
+        Ext_footnotes
+      ]
+
+pandocReaderOpts :: ReaderOptions
+pandocReaderOpts =
+  defaultHakyllReaderOptions
+    { readerExtensions = pandocExtensionsCustom
+    }
+
+pandocWriterOpts :: WriterOptions
+pandocWriterOpts =
+  defaultHakyllWriterOptions
+    { writerExtensions = pandocExtensionsCustom
+    }
+
+-- FEEDS
+
+type FeedRenderer =
+  FeedConfiguration ->
+  Context String ->
+  [Item String] ->
+  Compiler (Item String)
+
+feedCompiler :: FeedRenderer -> Compiler (Item String)
+feedCompiler renderer =
+  renderer feedConfiguration feedCtx
+    =<< recentFirst
+    =<< loadAllSnapshots "posts/*" "content"
+
+feedConfiguration :: FeedConfiguration
+feedConfiguration =
+  FeedConfiguration
+    { feedTitle = "My Site",
+      feedDescription = "My Site Description",
+      feedAuthorName = "My Name",
+      feedAuthorEmail = "me@myemail.com",
+      feedRoot = root
+    }
+
+-- CUSTOM ROUTE
+
+getTitleFromMeta :: Metadata -> String
+getTitleFromMeta =
+  fromMaybe "no title" . lookupString "title"
+
+fileNameFromTitle :: Metadata -> FilePath
+fileNameFromTitle =
+  T.unpack . (`T.append` ".html") . toSlug . T.pack . getTitleFromMeta
+
+titleRoute :: Metadata -> Routes
+titleRoute =
+  constRoute . fileNameFromTitle
diff --git a/generator/src/Slug.hs b/generator/src/Slug.hs
new file mode 100644
index 0000000..08d2d79
--- /dev/null
+++ b/generator/src/Slug.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Slug
+  ( toSlug,
+  )
+where
+
+import Data.Char (isAlphaNum)
+import qualified Data.Text as T
+
+keepAlphaNum :: Char -> Char
+keepAlphaNum x
+  | isAlphaNum x = x
+  | otherwise = ' '
+
+clean :: T.Text -> T.Text
+clean =
+  T.map keepAlphaNum . T.replace "'" "" . T.replace "&" "and"
+
+toSlug :: T.Text -> T.Text
+toSlug =
+  T.intercalate (T.singleton '-') . T.words . T.toLower . clean
diff --git a/nix/default.nix b/nix/default.nix
new file mode 100644
index 0000000..361254b
--- /dev/null
+++ b/nix/default.nix
@@ -0,0 +1,43 @@
+let
+  sources = import ./sources.nix;
+  config = { allowBroken = true; };
+in
+{ pkgs ? import sources.nixpkgs { inherit config; } }:
+
+  let
+    pre-commit-hooks = import sources."pre-commit-hooks.nix";
+    haskellPackages = pkgs.callPackage ../generator/hpkgs.nix {};
+    generator = haskellPackages.callPackage ../generator/default.nix {};
+    src = ../src;
+  in
+    {
+      inherit generator haskellPackages pkgs src;
+
+      tools = [
+        # uncomment pkgs.cacert & pkgs.nix if nix-shell --pure
+        # (https://github.com/nmattia/niv/issues/222)
+
+        #pkgs.cacert
+        #pkgs.nix
+        generator
+        pkgs.niv
+        pkgs.pre-commit
+        pre-commit-hooks.hlint
+        pre-commit-hooks.nixpkgs-fmt
+        pre-commit-hooks.ormolu
+      ];
+
+      ci = {
+        pre-commit-check = pre-commit-hooks.run {
+          inherit src;
+
+          hooks = {
+            nix-linter.enable = true;
+            nixpkgs-fmt.enable = true;
+            ormolu.enable = true;
+            shellcheck.enable = true;
+          };
+          excludes = [ "^nix/sources\.nix$" ];
+        };
+      };
+    }
diff --git a/nix/sources.json b/nix/sources.json
new file mode 100644
index 0000000..d00e093
--- /dev/null
+++ b/nix/sources.json
@@ -0,0 +1,38 @@
+{
+    "niv": {
+        "branch": "master",
+        "description": "Easy dependency management for Nix projects",
+        "homepage": "https://github.com/nmattia/niv",
+        "owner": "nmattia",
+        "repo": "niv",
+        "rev": "dd13098d01eaa6be68237e7e38f96782b0480755",
+        "sha256": "1cfjdbsn0219fjzam1k7nqzkz8fb1ypab50rhyj026qbklqq2kvq",
+        "type": "tarball",
+        "url": "https://github.com/nmattia/niv/archive/dd13098d01eaa6be68237e7e38f96782b0480755.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "nixpkgs": {
+        "branch": "nixos-20.09",
+        "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to",
+        "homepage": "https://github.com/NixOS/nixpkgs",
+        "owner": "NixOS",
+        "repo": "nixpkgs-channels",
+        "rev": "a40c69ed4768fe8bfadebc083cd60a01a4e59263",
+        "sha256": "0izmsc96l9qjypr4smhpzbrqxikg0d8zi9wn40w5cydy8ya88n2i",
+        "type": "tarball",
+        "url": "https://github.com/NixOS/nixpkgs-channels/archive/a40c69ed4768fe8bfadebc083cd60a01a4e59263.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "pre-commit-hooks.nix": {
+        "branch": "master",
+        "description": "Seamless integration of https://pre-commit.com git hooks with Nix.",
+        "homepage": "",
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "rev": "a3f7609ba73eb9ce3fe434d4339101017a430912",
+        "sha256": "05mi3s7c30wylfyxfb3hab0w9cbjahskmz4abidbxsmchjyici6a",
+        "type": "tarball",
+        "url": "https://github.com/cachix/pre-commit-hooks.nix/archive/a3f7609ba73eb9ce3fe434d4339101017a430912.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    }
+}
diff --git a/nix/sources.nix b/nix/sources.nix
new file mode 100644
index 0000000..8a725cb
--- /dev/null
+++ b/nix/sources.nix
@@ -0,0 +1,134 @@
+# This file has been generated by Niv.
+
+let
+
+  #
+  # The fetchers. fetch_<type> fetches specs of type <type>.
+  #
+
+  fetch_file = pkgs: spec:
+    if spec.builtin or true then
+      builtins_fetchurl { inherit (spec) url sha256; }
+    else
+      pkgs.fetchurl { inherit (spec) url sha256; };
+
+  fetch_tarball = pkgs: spec:
+    if spec.builtin or true then
+      builtins_fetchTarball { inherit (spec) url sha256; }
+    else
+      pkgs.fetchzip { inherit (spec) url sha256; };
+
+  fetch_git = spec:
+    builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; };
+
+  fetch_builtin-tarball = spec:
+    builtins.trace
+      ''
+        WARNING:
+          The niv type "builtin-tarball" will soon be deprecated. You should
+          instead use `builtin = true`.
+
+          $ niv modify <package> -a type=tarball -a builtin=true
+      ''
+      builtins_fetchTarball { inherit (spec) url sha256; };
+
+  fetch_builtin-url = spec:
+    builtins.trace
+      ''
+        WARNING:
+          The niv type "builtin-url" will soon be deprecated. You should
+          instead use `builtin = true`.
+
+          $ niv modify <package> -a type=file -a builtin=true
+      ''
+      (builtins_fetchurl { inherit (spec) url sha256; });
+
+  #
+  # Various helpers
+  #
+
+  # The set of packages used when specs are fetched using non-builtins.
+  mkPkgs = sources:
+    let
+      sourcesNixpkgs =
+        import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {};
+      hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
+      hasThisAsNixpkgsPath = <nixpkgs> == ./.;
+    in
+      if builtins.hasAttr "nixpkgs" sources
+      then sourcesNixpkgs
+      else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
+        import <nixpkgs> {}
+      else
+        abort
+          ''
+            Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
+            add a package called "nixpkgs" to your sources.json.
+          '';
+
+  # The actual fetching function.
+  fetch = pkgs: name: spec:
+
+    if ! builtins.hasAttr "type" spec then
+      abort "ERROR: niv spec ${name} does not have a 'type' attribute"
+    else if spec.type == "file" then fetch_file pkgs spec
+    else if spec.type == "tarball" then fetch_tarball pkgs spec
+    else if spec.type == "git" then fetch_git spec
+    else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec
+    else if spec.type == "builtin-url" then fetch_builtin-url spec
+    else
+      abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
+
+  # Ports of functions for older nix versions
+
+  # a Nix version of mapAttrs if the built-in doesn't exist
+  mapAttrs = builtins.mapAttrs or (
+    f: set: with builtins;
+    listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
+  );
+
+  # fetchTarball version that is compatible between all the versions of Nix
+  builtins_fetchTarball = { url, sha256 }@attrs:
+    let
+      inherit (builtins) lessThan nixVersion fetchTarball;
+    in
+      if lessThan nixVersion "1.12" then
+        fetchTarball { inherit url; }
+      else
+        fetchTarball attrs;
+
+  # fetchurl version that is compatible between all the versions of Nix
+  builtins_fetchurl = { url, sha256 }@attrs:
+    let
+      inherit (builtins) lessThan nixVersion fetchurl;
+    in
+      if lessThan nixVersion "1.12" then
+        fetchurl { inherit url; }
+      else
+        fetchurl attrs;
+
+  # Create the final "sources" from the config
+  mkSources = config:
+    mapAttrs (
+      name: spec:
+        if builtins.hasAttr "outPath" spec
+        then abort
+          "The values in sources.json should not have an 'outPath' attribute"
+        else
+          spec // { outPath = fetch config.pkgs name spec; }
+    ) config.sources;
+
+  # The "config" used by the fetchers
+  mkConfig =
+    { sourcesFile ? ./sources.json
+    , sources ? builtins.fromJSON (builtins.readFile sourcesFile)
+    , pkgs ? mkPkgs sources
+    }: rec {
+      # The sources, i.e. the attribute set of spec name to spec
+      inherit sources;
+
+      # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
+      inherit pkgs;
+    };
+in
+mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..80355bd
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,14 @@
+let
+  sources = import ./nix/sources.nix;
+in
+{ pkgs ? import sources.nixpkgs {} }:
+
+  let
+    cfg = import ./nix/default.nix {};
+  in
+    pkgs.mkShell {
+      buildInputs = cfg.tools;
+      shellHook = ''
+        ${cfg.ci.pre-commit-check.shellHook}
+      '';
+    }
diff --git a/src/CNAME b/src/CNAME
new file mode 100644
index 0000000..2534915
--- /dev/null
+++ b/src/CNAME
@@ -0,0 +1 @@
+mywebsite.com
diff --git a/src/_config.yaml b/src/_config.yaml
new file mode 100644
index 0000000..7a30e5f
--- /dev/null
+++ b/src/_config.yaml
@@ -0,0 +1 @@
+include: []
diff --git a/src/css/article.css b/src/css/article.css
new file mode 100644
index 0000000..97e078b
--- /dev/null
+++ b/src/css/article.css
@@ -0,0 +1,44 @@
+:root {
+  font-size: 62.5%;
+  box-sizing: border-box;
+  -ms-text-size-adjust: 100%;
+  -webkit-text-size-adjust: 100%;
+}
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+html,
+body {
+  min-height: 100vh;
+}
+body {
+  font-kerning: normal;
+  -moz-font-feature-settings: "kern", "liga", "clig", "calt";
+  -ms-font-feature-settings: "kern", "liga", "clig", "calt";
+  -webkit-font-feature-settings: "kern", "liga", "clig", "calt";
+  font-feature-settings: "kern", "liga", "clig", "calt";
+  scroll-behavior: smooth;
+}
+article h1,
+article small,
+article p {
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+article small,
+article p {
+  font-family: Tahoma, Arial, sans-serif;
+}
+article h1 {
+  font-size: 4.0rem;
+}
+article small {
+  font-size: 1.6rem;
+  font-style: italic;
+}
+article p {
+  font-family: Tahoma, Arial, sans-serif;
+  font-size: 1.8rem;
+}
diff --git a/src/css/default.css b/src/css/default.css
new file mode 100644
index 0000000..7a3a707
--- /dev/null
+++ b/src/css/default.css
@@ -0,0 +1,30 @@
+:root {
+  font-size: 62.5%;
+  box-sizing: border-box;
+  -ms-text-size-adjust: 100%;
+  -webkit-text-size-adjust: 100%;
+}
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+html,
+body {
+  min-height: 100vh;
+}
+body {
+  font-kerning: normal;
+  -moz-font-feature-settings: "kern", "liga", "clig", "calt";
+  -ms-font-feature-settings: "kern", "liga", "clig", "calt";
+  -webkit-font-feature-settings: "kern", "liga", "clig", "calt";
+  font-feature-settings: "kern", "liga", "clig", "calt";
+  scroll-behavior: smooth;
+}
+.ffs { font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif; }
+.ffss { font-family: Tahoma, Arial, sans-serif; }
+.fs { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
+.fs14 { font-size: 1.4rem; }
+.fs18 { font-size: 1.8rem; }
+.fs32 { font-size: 3.2rem; }
+.fs40 { font-size: 4.0rem; }
diff --git a/src/favicon.ico b/src/favicon.ico
new file mode 100644
index 0000000..2096d33
Binary files /dev/null and b/src/favicon.ico differ
diff --git a/src/images/robert-pearce-UwHN0jU_YqQ-unsplash-800w.jpg b/src/images/robert-pearce-UwHN0jU_YqQ-unsplash-800w.jpg
new file mode 100644
index 0000000..5a35972
Binary files /dev/null and b/src/images/robert-pearce-UwHN0jU_YqQ-unsplash-800w.jpg differ
diff --git a/src/images/waiheke-stony-batter.jpg b/src/images/waiheke-stony-batter.jpg
new file mode 100644
index 0000000..eb9211d
Binary files /dev/null and b/src/images/waiheke-stony-batter.jpg differ
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..e3a49b6
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,29 @@
+---
+description: "This is me saying hello to the world"
+image: "/images/robert-pearce-UwHN0jU_YqQ-unsplash-800w.jpg"
+lang: "en"
+stylesheet: "default"
+title: "Hello, world!"
+---
+
+<header>
+  <h1 class="ffs fs fs40">Hello, world!</h1>
+  <img
+    alt="A woman sitting on a bench amongst trees at the end of a boardwalk leading to a pond with mountains in the background"
+    src="/images/robert-pearce-UwHN0jU_YqQ-unsplash-800w.jpg"
+    style="max-width:500px;"
+  />
+</header>
+<main>
+  <section>
+    <h2 class="ffs fs fs32">Blog Posts</h2>
+    <ul>
+      $for(posts)$
+        <li>
+          <div><a href="$url$" class="ffss fs fs18">$title$</a></div>
+          <small class="ffss fs fs14">$date$</small>
+        </li>
+      $endfor$
+    </ul>
+  </section>
+</main>
diff --git a/src/js/script.js b/src/js/script.js
new file mode 100644
index 0000000..4fc48a9
--- /dev/null
+++ b/src/js/script.js
@@ -0,0 +1,3 @@
+;(function(w, d) {
+  console.log('Hello, world!', w, d)
+})(window, document);
diff --git a/src/posts/2020-09-21-hello-world.md b/src/posts/2020-09-21-hello-world.md
new file mode 100644
index 0000000..82674d7
--- /dev/null
+++ b/src/posts/2020-09-21-hello-world.md
@@ -0,0 +1,19 @@
+---
+author: "My name"
+authorTwitter: "@MyName"
+description: "I announce myself to the world"
+image: "/images/waiheke-stony-batter.jpg"
+keywords: "hello, announcement"
+lang: "en"
+stylesheet: "article"
+title: "Hello, world!"
+updated: "2020-09-22T12:00:00Z"
+---
+
+Hello, world! I am here!
+
+<img
+  alt="Grapevines among rolling hills leading to the sea"
+  src="/images/waiheke-stony-batter.jpg"
+  style="max-width:500px;"
+/>
diff --git a/src/posts/2020-09-22-hola-mundo.md b/src/posts/2020-09-22-hola-mundo.md
new file mode 100644
index 0000000..9a01c80
--- /dev/null
+++ b/src/posts/2020-09-22-hola-mundo.md
@@ -0,0 +1,19 @@
+---
+author: "Mi nombre"
+authorTwitter: "@MiNombre"
+description: "Me anuncio al mundo"
+image: "/images/waiheke-stony-batter.jpg"
+keywords: "hola, anuncio"
+lang: "es"
+stylesheet: "article"
+title: "¡Hola Mundo!"
+updated: "2020-09-23T12:00:00Z"
+---
+
+¡Hola Mundo! ¡Estoy aquí!
+
+<img
+  alt="Grapevines among rolling hills leading to the sea"
+  src="/images/waiheke-stony-batter.jpg"
+  style="max-width:500px;"
+/>
diff --git a/src/robots.txt b/src/robots.txt
new file mode 100644
index 0000000..eb05362
--- /dev/null
+++ b/src/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/src/templates/default.html b/src/templates/default.html
new file mode 100644
index 0000000..fe7bd0a
--- /dev/null
+++ b/src/templates/default.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html lang="$lang$">
+  <head>
+    <title>$title$</title>
+
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="description" content="$description$">
+    $if(author)$
+    <meta name="author" content="$author$">
+    $endif$
+    $if(keywords)$
+    <meta name="keywords" content="$keywords$">
+    $endif$
+
+    <meta property="og:site_name" content="$siteName$">
+    <meta property="og:title" content="$title$">
+    <meta property="og:url" content="$root$$url$">
+    <meta property="og:description" content="$description$">
+    $if(image)$
+    <meta property="og:image" content="$root$$image$">
+    $endif$
+    $if(type)$
+    <meta property="og:type" content="$type$">
+    $else$
+    <meta property="og:type" content="website">
+    $endif$
+
+    <meta property="twitter:card" content="summary_large_image">
+    <meta property="twitter:site" content="$siteName$">
+    <meta property="twitter:title" content="$title$">
+    <meta property="twitter:description" content="$description$">
+    $if(image)$
+    <meta property="twitter:image" content="$root$$image$">
+    $endif$
+    $if(authorTwitter)$
+    <meta property="twitter:creator" content="$authorTwitter$">
+    $endif$
+
+    <link rel="shortcut icon" href="/favicon.ico">
+    <link rel="canonical" href="$root$$url$">
+    <link rel="stylesheet" href="/css/$stylesheet$.css" />
+  </head>
+  <body>
+    $body$
+    <script async src="/js/script.js"></script>
+  </body>
+</html>
diff --git a/src/templates/post.html b/src/templates/post.html
new file mode 100644
index 0000000..5809bb8
--- /dev/null
+++ b/src/templates/post.html
@@ -0,0 +1,18 @@
+<main>
+  <article>
+    <header>
+      <h1>
+        <a href="$url$">$title$</a>
+      </h1>
+      <div>
+        <small>$date$</small>
+        $if(updated)$
+        <small>(updated: $updated$)</small>
+        $endif$
+      </div>
+    </header>
+    <section>
+      $body$
+    </section>
+  </article>
+</main>
diff --git a/src/templates/sitemap.xml b/src/templates/sitemap.xml
new file mode 100644
index 0000000..10b9945
--- /dev/null
+++ b/src/templates/sitemap.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
+  <url>
+    <loc>$root$</loc>
+    <changefreq>daily</changefreq>
+    <priority>1.0</priority>
+  </url>
+$for(pages)$
+  <url>
+    <loc>$root$$url$</loc>
+    <lastmod>$if(updated)$$updated$$else$$if(date)$$date$$endif$$endif$</lastmod>
+    <changefreq>weekly</changefreq>
+    <priority>0.8</priority>
+  </url>
+$endfor$
+</urlset>