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#
Install Babel dependencies:
bash npm init -y npm install @babel/cli @babel/core @babel/preset-react --saveCreate
babel.config.jsin your Hugo root:javascript module.exports = function (api) { api.cache(true); const presets = [["@babel/preset-react"]]; const plugins = []; return { presets, plugins }; }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} />); }); });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:
- Creates a container div with a unique ID for the React component
- Loads React and ReactDOM from a CDN (only once per page)
- Transpiles JSX to JavaScript using Hugo’s Babel pipe during build
- Loads the transpiled component as a regular JavaScript file
- 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! ๐