Introduction Link to heading

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? Link to heading

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 Link to heading

Example 1: Counter Component Link to heading

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 Link to heading

A more complex example - a fully functional todo list:

How It Works Link to heading

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

Setup Steps Link to heading

  1. Install Babel dependencies:

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

    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/:

    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:

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

How the Shortcode Works Link to heading

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 Link to heading

  • 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 Link to heading

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 Link to heading

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 Link to heading

Happy coding! 🚀