Skip to content

Instantly share code, notes, and snippets.

@Oziabr
Last active August 5, 2025 07:41
Show Gist options
  • Select an option

  • Save Oziabr/3fcdf0510806fc516b12f815ab8d2d91 to your computer and use it in GitHub Desktop.

Select an option

Save Oziabr/3fcdf0510806fc516b12f815ab8d2d91 to your computer and use it in GitHub Desktop.
htmx with pug/jade showcase (for r/htmx)

htmx with pug/jade showcase

Origin of this story is me being lazy, using JS for every practical application because it's cheap. Naturally, JS has the best template engine for the web (almost exclusively; Golang has an analog, but it’s painful to use). The beauty of Pug/Jade is its YAML-like indentation-based structure, which guarantees valid HTML output. The only way to break HTML in Pug is to render raw, unescaped output - a rare necessity if you know what you’re doing. This is what stopped me from adopting Golang’s tmpl: I never figured out how to validate templates without resorting to an overly costly e2e test suite.

Here is the essential example of how I'm using it:

    // table.js
    app.get('/:table', supa_read, (req, res) => {
      if (req.query.type == 'part' || req.headers['hx-request'])
        return res.render('part/table')
      if (req.query.type == 'json' || req.headers.accept == 'application/json')
        return res.json(res.locals.rows)
      res.render('table')
    })
    // views/table.pug
    html
      body
        div(hx-target="this" hx-indicator="#load")
          include part/table.pug
    // views/part/table.pug
    mixin a
      a(
        hx-get!=attributes.href
        hx-push-url="true"
      )&attributes(attributes)
        block
    
    table.table
      tbody
        each row in rows
          tr
            each key in shown
              td #{row[key]}
      caption
        nav.ms-auto
          ul.pagination
            each n in [...Array(pages).keys()]
              li.page-item(class=(n == page ? 'active' : ''))
                +a.page-link(href=`/${table}?${sp({page: n})}`)= n+1

Key highlights:

  • part/table.pug contains all representation logic for the region, eliminating the need for OOB hacks
  • It’s rendered both from a parent template and independently for HTMX, with little difference from standard template use cases.
  • Data can be rendered as JSON if requested (this solved a major headache for me when adopting HTMX - lack of API-friendly output).
  • mixin a extracts href while preserving everithing else, reducing template wrighting overhead to a single + character (the same can be applied to forms)

I'm really happy with this setup: it's simplicity, universality and modern UX.

If you new to the concept please check pugjs.org and html2jade.org

Please share what do you think about it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment