Skip to Content
Shafie Mukhre

3 mins read


How to use Preact in Hugo with ESBuild


Hugo is one of the most popular static site generator (SSG) and it is written in Go. It is fast and easy to use. There are several factors to consider when to use static SSG site (Hugo, Jekyll) and when to use dynamic site (Next.js, Gatsby, Vue) for the purpose of marketing website and documentation site.

Hugo is great for minimal static websites, but we still need some interactivity and we would prefer to not use vanilla javascript or jQuery. With Preact, we can create reusable lightweight react components only when need it. It is a good balance for a website with lots of static contents and some interactive components, that way the team can focus more on writing contents for SEO purposes and less work on maintaining website. Another alternative option is Alpine.js (kinda like modern jQuery).

Demo: https://preact-hugo-esbuild.netlify.app/

Tutorial

In order to build Preact app with ESBuild, we need to add JSX Factory and JSX Fragment options.

This repo is a minimal example on how to use Preact in Hugo using js.Build (implemented with ESBuild). Refer to the documentation: https://gohugo.io/hugo-pipes/js#options

The solution is to add string value h for JSXFactory and string value Fragment for JSXFragment options key as per Hugo source code here.

This will let ESBuild to use h instead of React.createElement and use Fragment instead of React.Fragment.

./layouts/index.html

{{ with resources.Get "index.jsx" }} {{ $defines := dict "process.env.NODE_ENV" "\"development\"" "process.env.BaseURL"
(printf `"%s"` $.Site.BaseURL) }} {{ $options := dict "defines" $defines "JSXFactory" "h" "JSXFragment" "Fragment"}} {{
$script := . | js.Build $options }}
<script src="{{ $script.RelPermalink }}" defer></script>
{{end}}

./assets/index.js

import { Fragment, h, render } from "preact";
import { useState } from "preact/hooks";
 
const Counter = () => {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  // You can also pass a callback to the setter
  const decrement = () => setCount((currentCount) => currentCount - 1);
 
  return (
    <>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </>
  );
};
 
//Render Preact component in a single root
render(<Counter />, document.getElementById("root"));
 
//Render Preact component in multiple roots
const roots = document.querySelectorAll(".widget");
roots.forEach((root) => {
  render(<Counter />, root);
});