Work in this file

1. Publish

(defun jdb/org-html-postamble (plist)
  (format "Last update: %s" (format-time-string "%d %b %Y")))
(setq org-html-postamble 'jdb/org-html-postamble)

(setq org-html-doctype "html5")
(setq org-html-html5-fancy t)
(defun jdb/html-publish-to-html (plist filename pub-dir)
  "Publish an org file to HTML.

  FILENAME is the filename of the Org file to be published.
PLIST is the property list for the given project.
PUB-DIR is the publishing directory.

  Return output file name."
  (org-publish-org-to 'jdb/html filename
                      (concat (when (> (length org-html-extension) 0) ".")
                              (or (plist-get plist :html-extension)
                                  org-html-extension
                                  "html"))
                      plist pub-dir))
(setq org-publish-project-alist
      '(("blog"
         :auto-sitemap t
         :sitemap-filename "index.org"
         :sitemap-title "Blog"

         :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"../reset.css\" />"
         :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"../stylesheet.css\" />"

         :base-directory "./blog"
         :language "en"
         :publishing-function jdb/html-publish-to-html
         :publishing-directory "./blog"
         :section-numbers t
         :with-toc t)))

(jdb/export-to-html)
(org-publish "blog" t)

1.1. Work around hardcoded strings

language: en

#+name org-export-dictionary

(defconst org-export-dictionary
  '(("Table of Contents"
     ("en" :html "Table of contents" :utf-8 "Table of contents"))))

1.2. Use HTML5

From HTML Doctypes.

Org’s HTML exporter does not by default enable new block elements introduced with the HTML5 standard. To enable them, set org-html-html5-fancy to non-nil. Or use an ‘OPTIONS’ line in the file to set ‘html5-fancy’.

(setq org-html-doctype "html5")
(setq org-html-html5-fancy t)

1.3. Define transcode functions

(defun jdb/html--parse-string (str)
  "Parse STR and return a DOM."
  (with-temp-buffer
    (insert (or str ""))
    (libxml-parse-html-region (point-min) (point-max))))

(defun jdb/html--build-body (contents info &rest other)
  "Return information for the <body>..</body> of the HTML output.
     CONTENTS is the transcoded contents string.
     INFO is a plist used as a communication channel.
     OTHER is a plist for additional nodes."
  (let ((node-body (dom-node "body" ()))
        (node-layout (dom-node "div" '((id . "layout") (class . "layout"))))
        (node-main (dom-node "main" '((id . "content") (class . "content"))))
        (node-toc (dom-node "div" '((id . "toc") (class . "toc")))))
    (when (plist-get other :before)
      (dolist (node (plist-get other :before)) (dom-append-child node-layout node)))
    (dolist (node (dom-non-text-children (dom-by-tag (jdb/html--parse-string contents) 'body)))
      (cond ((string= (dom-tag node) "nav") (dom-append-child node-toc node))
            (t (dom-append-child node-main node))))
    (when (plist-get other :after)
      (dolist (node (plist-get other :after)) (dom-append-child node-layout node)))
    (dom-append-child node-layout node-main)
    (dom-append-child node-layout node-toc)
    (dom-append-child node-body node-layout)))

(defun jdb/html--build-head (info &rest other)
  "Return information for the <head>..</head> of the HTML output.
     INFO is a plist used as a communication channel.
     OTHER is a plist for additional nodes."
  (setq node-head (dom-node "head" ()))
  (when (plist-get other :before)
    (dolist (node (plist-get other :before)) (dom-append-child node-head node)))
  (dolist (node (dom-children (dom-by-tag (jdb/html--parse-string (plist-get info :html-head)) 'head)))
    (dom-append-child node-head node))
  (dolist (node (dom-children (dom-by-tag (jdb/html--parse-string (plist-get info :html-head-extra)) 'head)))
    (dom-append-child node-head node))
  (when (and (plist-get info :html-htmlized-css-url)
             (eq org-html-htmlize-output-type 'css))
    (dom-append-child node-head
                      (dom-node "link" `((rel . "stylesheet")
                                         (href . ,(plist-get info :html-htmlized-css-url))
                                         (type . "text/css")))))
  (when (plist-get other :after)
    (dolist (node (plist-get other :after)) (dom-append-child node-head node)))
  node-head)

(defun jdb/html-template (contents info)
  "Return complete document string after HTML conversion.
     CONTENTS is the transcoded contents string.
     INFO is a plist holding export options."
  (let* ((title (org-export-data (plist-get info :title) info))
         (node-title (dom-node "meta" `((title . ,title))))
         (node-charset (dom-node "meta" '((charset . "utf-8"))))
         (node-author (dom-node "meta" `((author . ,(org-export-data (plist-get info :author) info)))))
         (node-generator (dom-node "meta" '((generator . "Org"))))
         (node-head (jdb/html--build-head info :after (list node-charset node-title node-author node-generator)))
         (node-h1 (dom-node "h1" '((class . "title")) title))
         (node-header (dom-node "header" () node-h1))
         (node-body (jdb/html--build-body contents info :before (list node-header)))
         (node-html (dom-node "html" () node-head node-body)))
    (with-temp-buffer (insert "<!DOCTYPE html>") (dom-print node-html) (buffer-string))))
(org-export-define-derived-backend 'jdb/html 'html
  :translate-alist '((template . jdb/html-template)))

1.4. Define export functions

(defun jdb/export-subtree-to-html ()
  (interactive)
  (let ((filename (cdr (assoc "EXPORT_FILE_NAME" (org-entry-properties)))))
    (org-export-to-file 'jdb/html filename nil t)
    (shell-command (format "prettier -w --print-width 999 %s" filename))))
(defun jdb/export-to-html ()
  (interactive)
  (org-babel-tangle)
  (org-publish "blog")
  (org-map-entries #'jdb/export-subtree-to-html "EXPORT_FILE_NAME={.+}"))

1.5. Define cleaner postamble

(defun jdb/org-html-postamble (plist)
  (format "Last update: %s" (format-time-string "%d %b %Y")))
(setq org-html-postamble 'jdb/org-html-postamble)

2. Add a structure

C-c C-,

3. Tangle

C-c C-v t

4. Ensure pretty output

(add-hook 'org-babel-post-tangle-hook #'whitespace-cleanup)

5. Format buffer

C-x h C-M-\ C-u C-SPC C-u C-SPC

6. Add babel execute for Org src

Have Org src within the Org file.

(defun org-babel-execute:org (body params)
  "Execute a block of Org code with org-babel.
  BODY is the src code body.
  PARAMS isn't used."
  body)