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);
});