# Upgrading to Hinode v2

Upgrade your existing Hinode site to take advantage of the refactored architecture introduced in v2.

Added in v2.0.0

> [!NOTE]
> Still on Hinode v0.x? Check out [Upgrading to Hugo modules](upgrading-hinode-v0-16/) and [Upgrading to Hinode v1](upgrading-hinode-v1/) first.

Hinode Added in v2.0.0
 is a major architectural refactoring of the theme. Content blocks are now published as a standalone module, the Dart Sass transpiler is required, and several companion modules have bumped major versions with breaking changes. This guide targets users of the [Hinode template](https://github.com/gethinode/template) running a v1-compatible site. The template repository has been fully updated for v2 and can be used as a reference for any of the changes described below. Please use [GitHub discussions](https://github.com/gethinode/hinode/discussions) when encountering any challenges, or the [issue tracker](https://github.com/gethinode/hinode/issues) for bug reports.

## Meeting the updated prerequisites

Hugo Extended is still required. In addition, **Dart Sass** is now required — the `libsass` transpiler is no longer supported.

> [!IMPORTANT]
> Install Dart Sass on your local machine before upgrading. Download the latest release from the [Dart Sass releases page](https://github.com/sass/dart-sass/releases) and ensure the `sass` binary is available on your `PATH`. If you use CI/CD automation, see [Updating the CI/CD pipeline](#updating-the-cicd-pipeline) below.

The minimum Hugo version remains **0.146.0**, unchanged from v1.

## Updating the module imports

Hinode v2 uses a new Go module path (`hinode/v2`). Content blocks are also no longer bundled with the core theme — they are now published as a separate `mod-blocks` module.

Update `config/_default/hugo.toml` to replace the existing Hinode import and add `mod-blocks`:

Before:

```toml
[[module.imports]]
  path = "github.com/gethinode/hinode"
```

After:

```toml
[[module.imports]]
  path = "github.com/gethinode/mod-blocks"

[[module.imports]]
  path = "github.com/gethinode/hinode/v2"
```

> [!NOTE]
> The `mod-blocks` import is required if your site uses any of the following blocks or components: `hero`, `contact`, `faq`, `menu`, `testimonial-carousel`, or `section`. Sites that do not use content blocks can omit this import. The import order also matters, be sure to import `mod-blocks` before `hinode`.

Once the import paths are updated, run the following command to update `go.mod` and `go.sum` and vendor all modules:

```bash
npm run mod:update
```

## Switching to Dart Sass

Update the `[main.build]` block in `config/_default/params.toml` to use the Dart Sass transpiler:

Before:

```toml
[main.build]
  transpiler = "libsass"
```

After:

```toml
[main.build]
  transpiler = "dartsass"
  silenceDeprecations = true
```

The `silenceDeprecations = true` setting suppresses deprecation warnings emitted by Bootstrap's upstream Sass code during compilation.

## Reviewing companion module upgrades

Running `npm run mod:update` automatically resolves all module path changes. Review the following breaking changes in each module to determine whether your site is affected.

### mod-blocks

Content blocks — including `hero`, `contact`, `faq`, `menu`, `testimonial-carousel`, and `section` — were previously bundled with the core Hinode theme. They are now published as a standalone module at `github.com/gethinode/mod-blocks`. See [Updating the module imports](#updating-the-module-imports) above.

**Breaking change**: Block arguments now use `snake_case` instead of `kebab-case`. Update any content block definitions in your frontmatter accordingly. For example:

Before:

```yaml
- _bookshop_name: hero
  hide-empty: false
  header-style: none
```

After:

```yaml
- _bookshop_name: hero
  hide_empty: false
  header_style: none
```

### mod-fontawesome v5

The module path changed from `github.com/gethinode/mod-fontawesome/v4` to `github.com/gethinode/mod-fontawesome/v5`.

**Breaking change**: FontAwesome icons using `mode="svg"` are now rendered as build-time inline SVGs instead of being converted by a JavaScript runtime. Icons injected dynamically after page load via custom JavaScript will no longer be automatically converted to SVG. Migrate any such patterns to use static markup or the built-in Hinode shortcodes.

### mod-flexsearch v4

The module path changed from `github.com/gethinode/mod-flexsearch/v3` to `github.com/gethinode/mod-flexsearch/v4`. This version requires Hinode v2 and `mod-utils` v5. If you are not yet ready to upgrade to Hinode v2, stay on `mod-flexsearch` v3.

### mod-mermaid v4

The module path changed from `github.com/gethinode/mod-mermaid/v3` to `github.com/gethinode/mod-mermaid/v4`. This version introduces stricter argument validation. Review any custom Mermaid shortcode invocations and remove deprecated arguments flagged in the build output.

## Updating the npm toolchain

Several npm packages were updated or replaced. Apply the following changes to `package.json` and run `npm install` when done.

### Hugo binary

Replace `hugo-bin` with `hugo-extended`. The new package targets Hugo Extended directly without requiring the `buildTags` configuration block.

Remove from `dependencies` (and delete the top-level `"hugo-bin"` config object if present):

```json
"hugo-bin": "0.149.2"
```

Add to `dependencies`:

```json
"hugo-extended": "^0.158.0"
```

### ESLint

ESLint v10 drops support for the `neostandard` shareable config in favor of a flat config with explicit plugins.

Remove from `devDependencies`:

```json
"neostandard": "^0.12.2"
```

Add to `devDependencies`:

```json
"@eslint/js": "^10.0.1",
"@stylistic/eslint-plugin": "^5.10.0",
"eslint": "^10.1.0",
"globals": "^17.4.0"
```

### Stylelint

Upgrade to v17:

```json
"stylelint": "^17.5.0",
"stylelint-config-standard-scss": "^17.0.0"
```

### PostCSS and PurgeCSS

Upgrade `@fullhuman/postcss-purgecss` from v7 to v8 and remove `purgecss-whitelister` (replaced by an explicit safelist — see [Updating the PostCSS configuration](#updating-the-postcss-configuration)):

Remove from `dependencies`:

```json
"purgecss-whitelister": "^2.4.0"
```

Add or update in `dependencies`:

```json
"@fullhuman/postcss-purgecss": "^8.0.0",
"cssnano": "^7.1.3",
"cssnano-preset-advanced": "^7.0.11",
"postcss-cli": "^11.0.1"
```

### Husky and Commitlint (optional)

The template now ships with [Husky](https://typicode.github.io/husky/) v9 git hooks that enforce [Conventional Commits](https://www.conventionalcommits.org/). This is optional but recommended for consistency with the upstream template.

Add to `devDependencies`:

```json
"@commitlint/cli": "^20.5.0",
"@commitlint/config-conventional": "^20.5.0",
"husky": "^9.1.7"
```

Add a `prepare` script to `scripts`:

```json
"prepare": "node .husky/install.mjs"
```

Create the following three files in a `.husky/` directory at the repository root:

`.husky/install.mjs` — skips Husky in CI and production environments:

```js
// Skip Husky in CI and production
if (process.env.CI === 'true' || process.env.NODE_ENV === 'production') process.exit(0)
const husky = await import('husky')
husky.default()
```

`.husky/commit-msg` — validates commit message format:

```bash
npx --no -- commitlint --edit $1
```

`.husky/pre-commit` — runs linting before each commit:

```bash
npm test
```

## Migrating the ESLint configuration

Rename `eslint.config.js` to `eslint.config.mjs` and replace its contents. The new config uses ESM imports and the `@eslint/js` + `@stylistic/eslint-plugin` plugins instead of `neostandard`.

Before (`eslint.config.js`, CommonJS):

```js
'use strict'

module.exports = require('neostandard')({
    ignores: [
      'assets/js/vendor/**',
      'node_modules/**'
    ]
})
```

After (`eslint.config.mjs`, ESM):

```js
import js from '@eslint/js'
import stylistic from '@stylistic/eslint-plugin'
import globals from 'globals'

export default [
  js.configs.recommended,
  stylistic.configs.customize({
    indent: 2,
    quotes: 'single',
    semi: false,
    arrowParens: false,
    braceStyle: '1tbs',
    commaDangle: 'never'
  }),
  {
    languageOptions: {
      globals: {
        ...globals.browser
      }
    },
    rules: {
      '@stylistic/space-before-function-paren': ['error', 'always'],
      '@stylistic/arrow-parens': ['error', 'as-needed'],
      '@stylistic/operator-linebreak': ['error', 'after', { overrides: { '?': 'before', ':': 'before' } }],
      '@stylistic/max-statements-per-line': 'off',
      'no-unused-vars': ['error', { vars: 'all', args: 'after-used', argsIgnorePattern: '^_', ignoreRestSiblings: true }]
    }
  },
  {
    ignores: [
      'assets/js/vendor/**',
      'node_modules/**'
    ]
  }
]
```

Delete the old `eslint.config.js` once the new file is in place.

## Updating the PostCSS configuration

The `config/postcss.config.js` safelist approach changed significantly. The previous config used `purgecss-whitelister` to scan SCSS files in `_vendor/` by path. Those paths reference the `hinode` module (not `hinode/v2`) and will no longer resolve after upgrading.

Replace the old whitelister approach with an explicit pattern-based safelist. At minimum, update the following:

1. Remove the `purgecss-whitelister` import and `whitelister([...])` call.
2. Replace the flat `safelist` array with a structured object containing `standard`, `deep`, and `greedy` arrays.
3. Expand `dynamicAttributes` to include `'data-bs-main-theme'` and `'data-transparent'`:

```js
dynamicAttributes: ['data-bs-theme', 'data-bs-main-theme', 'data-bs-theme-animate', 'data-transparent', 'role'],
```

Copy the full reference `postcss.config.js` from the [Hinode template repository](https://github.com/gethinode/template/blob/main/config/postcss.config.js) to ensure all component patterns are covered, including patterns for the new `testimonial-`, `preview-`, and `d-none-main` classes introduced in v2.

## Updating the CI/CD pipeline

If your site uses GitHub Actions, update `.github/workflows/lint-build.yml` to install Dart Sass and upgrade the Go and cache actions.

Add the `DART_SASS_VERSION` environment variable at the top of the workflow (or job):

```yaml
env:
  DART_SASS_VERSION: "1.98.0"
```

Add a Dart Sass installation step before the Hugo build step. The following example handles Linux, macOS, and Windows:

```yaml
- name: Install Dart Sass
  shell: bash
  run: |
    case "${{ runner.os }}" in
      Linux)
        URL="https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz"
        curl -fsSL "$URL" | tar -xz -C /usr/local/bin --strip-components=1 dart-sass/sass
        ;;
      macOS)
        URL="https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-macos-x64.tar.gz"
        curl -fsSL "$URL" | tar -xz -C /usr/local/bin --strip-components=1 dart-sass/sass
        ;;
      Windows)
        URL="https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-windows-x64.zip"
        curl -fsSL "$URL" -o dart-sass.zip
        unzip -q dart-sass.zip
        echo "$PWD/dart-sass" >> $GITHUB_PATH
        ;;
    esac
```

Update the Go setup step to use the stable release:

```yaml
- name: Set up Go
  uses: actions/setup-go@v5
  with:
    go-version: "stable"
```

Upgrade the cache action from v4 to v5:

```yaml
- uses: actions/cache@v5
```

## Updating the Netlify configuration

If you deploy to Netlify, update the environment variable pins in `netlify.toml`:

```toml
[build.environment]
  DART_SASS_VERSION = "1.98.0"
  HUGO_VERSION      = "0.158.0"
  NODE_VERSION      = "24.12.0"
```

The `DART_SASS_VERSION` variable is picked up by the Netlify build image to install Dart Sass before the Hugo build runs.
