Skip to content
3 min read series / engineering

Building Interactive Components with React and Hugo

Learn how to integrate React components into your Hugo static site for interactive features

Introduction#

Static site generators like Hugo are fantastic for creating fast, SEO-friendly websites. However, sometimes you need interactive components that go beyond static HTML. In this post, I’ll show you how to seamlessly integrate React components into your Hugo blog.

Why Combine Hugo and React?#

Hugo gives you:

  • โšก Lightning-fast build times
  • ๐Ÿ” Great SEO out of the box
  • ๐Ÿ“ Simple content management with Markdown

React adds:

  • ๐ŸŽจ Interactive UI components
  • โš›๏ธ Component-based architecture
  • ๐Ÿ”„ Dynamic state management

Together, they create a powerful hybrid approach: static content with dynamic islands of interactivity.

Interactive Examples#

Example 1: Counter Component#

Here’s a simple interactive counter built with React:

The counter above is a fully functional React component embedded in this static Hugo page!

Example 2: Todo List Application#

A more complex example - a fully functional todo list:

How It Works#

The integration uses Babel.js to transpile JSX during the Hugo build process, following best practices:

Setup Steps#

  1. Install Babel dependencies:

    bash
    npm init -y
    npm install @babel/cli @babel/core @babel/preset-react --save
  2. Create babel.config.js in your Hugo root:

    javascript
    module.exports = function (api) {
      api.cache(true);
      const presets = [["@babel/preset-react"]];
      const plugins = [];
      return { presets, plugins };
    }
  3. Create React components in /assets/js/react-apps/:

    jsx
    const { useState } = React;
    
    function YourComponent({ initialProp = 0 }) {
      const [state, setState] = useState(initialProp);
    
      return (
        <div>
          {/* Your component JSX */}
        </div>
      );
    }
    
    // Auto-mount when the script loads
    document.addEventListener('DOMContentLoaded', function() {
      const containers = document.querySelectorAll('[data-component="your-component"]');
      containers.forEach(container => {
        const props = JSON.parse(container.getAttribute('data-props') || '{}');
        const root = ReactDOM.createRoot(container);
        root.render(<YourComponent {...props} />);
      });
    });
  4. Use the shortcode in your markdown:

    markdown
    {{< react-component name="counter" id="my-counter" props='{"initialCount": 10}' >}}

How the Shortcode Works#

The custom Hugo shortcode:

  1. Creates a container div with a unique ID for the React component
  2. Loads React and ReactDOM from a CDN (only once per page)
  3. Transpiles JSX to JavaScript using Hugo’s Babel pipe during build
  4. Loads the transpiled component as a regular JavaScript file
  5. Auto-mounts the component when the page loads

Benefits of This Approach#

  • โœ… Proper Build-Time Transpilation - JSX is converted to JavaScript during Hugo build
  • โœ… No Browser-Side Babel - Faster page loads without Babel Standalone (~2MB!)
  • โœ… Progressive Enhancement - Static content works without JavaScript
  • โœ… Selective Loading - Only loads React when components are used
  • โœ… Easy to Use - Simple shortcode syntax
  • โœ… Flexible - Can pass props as JSON
  • โœ… Production Ready - Transpiled code is optimized and cached

Performance Considerations#

This approach offers excellent performance:

  • โœ… JSX transpilation happens at build time, not in the browser
  • โœ… React and ReactDOM are loaded from CDN (~130KB gzipped)
  • โœ… Each component is transpiled and cached by Hugo
  • โœ… No extra libraries needed in the browser
  • โœ… Fast page loads and excellent SEO

Conclusion#

Combining Hugo’s speed with React’s interactivity gives you the best of both worlds. Your content remains fast and SEO-friendly while offering rich, interactive experiences where needed.

Try creating your own React components and embedding them in your Hugo posts!

Resources#

Happy coding! ๐Ÿš€