Hugo

Hugo it is framework for building static websites commonly based on Markdown files.

Adding React to Hugo

Method described below:

  • does not create active development enviroment. It is suited for once-written-no-longer-be-changing react components
  • uses webpack as additional step, adds complexity to your build process (of course)
  • assuming using typescript

Prerequisites:

  • node is installed
  • hugo is installed

Assuming:

  • your hugo site is building corectly
  • your working directory is hugo site (the place where config.toml or config.yml lies)

Installing packages

npm init
npm install react react-dom @types/react @types/react-dom --save
npm install webpack webpack-cli webpack-merge typescript ts-loader style-loader css-loader --save-dev

Create tsconfig.json

{
    "compilerOptions": {
      "outDir": "./assets/react/dist/",
      "sourceMap": true,
      "target": "es5",                         
      "module": "es6",
      "strict": true,
      "jsx": "react",
      "allowJs": true,
      "moduleResolution": "node",
      "allowSyntheticDefaultImports": true,
    },

    "exclude": [
      "./themes",
  ]
}

Create webpack.common.js. This config is shared betwen developement (debuging information), and production (small size and performance).

Components will be stored in ./assets/react/ directory, which

const path = require('path');

module.exports = {
    entry: {
        coffee_ratio: './assets/react/coffee_ratio/index.tsx'
    },
    module: {
        rules: [
            {
                test: /\.css$/i,
                use: ["style-loader", "css-loader"],
            },
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: [
                    path.resolve(__dirname, './node_modules/'),
                    path.resolve(__dirname, './themes/')
                ],
            },
        ],
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, './assets/react/dist/'),
    },
}; 

Create webpack.dev.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
}); 

Create webpack.prod.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production',
}); 

Add build commands to package.json

{
//...
    "scripts": {
      "build": "webpack --config webpack.dev.js",
      "buildprod": "webpack --config webpack.prod.js"
    },
//...
}

Create layouts/shortcodes/react.html

{{ if .Get "entry" }}
<div id="{{ .Get "entry" }}"></div>
{{ else }}
<div id="app"></div>
{{ end }}

{{- $scriptName := print "react/dist/"  (.Get "name") ".js" -}}
{{- $app := resources.Get $scriptName -}}
<script src="{{ $app.RelPermalink }}"></script> 

Complete Dockerfile with multi-stage

FROM node:17.6.0-alpine3.15 as node_builder

WORKDIR /app

# restoring node dependecies
ADD package-lock.json package.json ./
RUN npm install

# building assets
ADD tsconfig.json webpack.common.js webpack.prod.js ./
ADD assets ./assets
RUN npm run buildprod

FROM  peaceiris/hugo as builder
COPY --from=node_builder /app/assets/react/dist /app/assets/react/dist

WORKDIR /app

# building the rest of application
ADD . .
RUN hugo

# coping output to webserver
FROM nginx:1.21.6-alpine as server
COPY --from=builder /app/public /usr/share/nginx/html