HUGO + PostCSS + Netlifyでポートフォリオサイトを作った話

# はじめに

静的サイトジェネレーターの[HUGO](https://gohugo.io/)、Node.js製のCSSフレームワークである[PostCSS](https://postcss.org/)、ビルド + ホスティングサービスの[Netlify](https://www.netlify.com/)を使ってポートフォリオを作ったので、作り方を公開したいなと思い、作成しました。
https://shinya-sato.com/

![shinya-sato.com_.png](https://qiita-image-store.s3.amazonaws.com/0/244212/17986648-0ba7-2699-200d-55b98a496ed7.png)

今回は **「どうやって作ったか」** について書いているので、ポートフォリオの構成とかについては原則書かない事にしておきます。




# HUGO

[HUGO](https://gohugo.io/)とは、Go言語製の静的サイトジェネレーターです。
「シンプルだが多機能」という特徴があります。
生のhtmlをそのまま編集するのと同じ要領で、「関連記事の表示」「多言語対応」等の高等な機能も持ち合わせています。

#### 何故HUGOを選んだのか
結論からいうと、 **参考にしたポートフォリオサイトがHUGOで実装されていたから**です。
[Jekyll](https://jekyllrb.com/)はbuildが遅いし、カスタム投稿のページネーションは実装できないなど、痒いところに手が届かなかったので、他の静的サイトジェネレーターを探していました。
[StaticGen](https://www.staticgen.com/)を見たら、GithubのStar数も多いので使ってみました。

#### HUGOをインストールする
まずはhomebrewを使って、HUGOをインストールします。
```brew install hugo```

#### Repositoryをcloneする
さっそく作り始める、のではなく、 **参考にするRepositoryをcloneするところから** 始めます。
そして、 **技術を徹底的にパクリまくります。**




# HUGOでサイトを作り始める
ある程度技術を盗んだら、早速HUGOでサイトを作り始めます。

```terminal:
hugo new site directory_name
Congratulations! Your new Hugo site is created in /Users/cookboys/documents/directory_name.

Just a few more steps and you're ready to go:

1. Download a theme into the same-named folder.
   Choose a theme from https://themes.gohugo.io/, or
   create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
   with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".

Visit https://gohugo.io/ for quickstart guide and full documentation.
```

#### HUGOを立ち上げてみる

コマンドを叩いたら、HUGOのサイトが出来ていると思いますので、ディレクトりに移動します。
```cd directory_name```

ディレクトりに移動したら、コマンドを叩いて、HUGOを立ち上げてみたいと思います。
```hugo server -w```
![hugo-start.png](https://qiita-image-store.s3.amazonaws.com/0/244212/e3eb3afc-0def-ab50-9392-83d130c60830.png)
何も表示されていません（Jekyllと違って、サンプル画面すらありません）
HUGOは必要最小限の動作環境だけ提供されるので、ここから自分で環境を構築していきます。




# PostCSSを導入する
必要最小限の環境しかありませんので、AltCSSの環境も自分で用意する必要があります。
なので今回は、Node.js製のCSSフレームワークである、[PostCSS](https://postcss.org/)を導入してみたいと思います。

#### なぜPostCSSなのか
単刀直入に言うと、

- Sassに飽きたから
- トランスパイルが早いから
- 実績ある企業が採用しているから（Facebook, Github, [Qiita](https://qiita.com/morishitter/items/c8773b6ca69b3576626a), Taobao等）

#### そもそもPostCSSって何？
PostCSSとは、先ほど申した通り、Node.js製のCSSフレームワークです。
Node.js製といえば、[Stylus](http://stylus-lang.com/)というCSSプリプロセッサーがありますが、PostCSSはStylusと違い、**インストールしただけでは何もしれくれません！**
「CSSフレームワーク」の名の通り、PostCSSは**プラグインを追加して、自分独自のCSSプリプロセッサーを作る**ツールなのです。
なので、**プラグインを追加しないと、何もしれくれません。**

- Stylusの場合
![stylus.png](https://qiita-image-store.s3.amazonaws.com/0/244212/5f9d0e8d-dead-c88a-8d1b-f6e86c23bf0f.png)
Node.jsが、.styl形式で書かれたファイルを、CSSに変換する。

- Sassの場合
![sass.png](https://qiita-image-store.s3.amazonaws.com/0/244212/f25a0957-673d-5cf9-6194-f5c269101dd2.png)
Rubyが、.sass形式で書かれたファイルを、CSSに変換する。

- PostCSSの場合
![postcss.png](https://qiita-image-store.s3.amazonaws.com/0/244212/d7230c1b-c4a8-f85f-4f0e-7744cffd7fdf.png)
Node.jsが、ファイルをCSSに変換するのはStylusと同じですが、拡張子が.cssのままです。
当然ですが、**このままでは使えるCSSになりません。**
```@reset-global pc;```も、```@custom-media --smartphone(width <= 600px);```も、CSSの構文には存在しないからです。
これらを使えるようにするには、PostCSSのプラグインを追加しないといけません。



# PostCSSを使えるようにする
では実際に、PostCSSを使えるようにしていきたいと思います。

#### npmを導入する
HUGOのディレクトリで、以下のコマンドを実行して、npmを導入します。
```npm init```

#### yarnをインストールする
以下のコマンドを実行して、yarnをインストールします。
```npm install -g yarn```

#### PostCSSをインストールする
以下のコマンドを実行して、PostCSSをインストールします。
```yarn add postcss```

#### CLIをインストールする
以下のコマンドを実行して、postcss-cliをインストールします。
```yarn add postcss-cli```

#### CLIの設定をする
CLIを使うために、設定ファイルの作成と、スクリプトを設定します。

- postcss.config.js

```js:
module.exports = (ctx) => ({
  plugins: []
});
```

- package.json

```json:
{
  "scripts": {
    "start": "hugo server -w & postcss postcss/*.css -d static/assets/styles/ -w",
    "build": "hugo & postcss postcss/*.css -d docs/assets/styles/"
  },
  "dependencies": {
    "postcss": "^6.0.19",
    "postcss-cli": "^5.0.0"
  }
}
```

HUGOを実行するスクリプトと、PostCSSを実行するスクリプトを一緒にしました。
これで、```yarn start```を実行すると、HUGOとPostCSSが同時に起動します。



# PostCSSのプラグインをインストールする
さて、準備ができましたので、この画像のCSSを使えるようにするために、PostCSSのプラグインをインストールします。

![postcss02.png](https://qiita-image-store.s3.amazonaws.com/0/244212/ac74952d-8ba5-5f59-eec4-d65837fc80fc.png)

```terminal:
yarn add postcss-css-reset postcss-mixins postcss-simple-vars postcss-color-function postcss-nested postcss-custom-media postcss-media-minmax 
```

- postcss-css-reset
    - PostCSSでreset.cssを設定できる。

- postcss-mixins
    - Postcssでmixinsを設定できる。

- postcss-simple-vars
    - ```$```を使った変数を使うことができる。

- postcss-color-function
    - color変数を使うことができる。

- postcss-nested
    - PostCSSでNest（入れ子）を使うことができる。

- postcss-custom-media
    - ```@media```に名前をつけてコンポーネント化することができる。

- postcss-media-minmax
    - ```@media```を不等号で指定できる。

#### configファイルに設定を記載する
プラグインを追加しただけではまだダメなので、```postcss.config.js```に設定を記載します。

```js:
module.exports = (ctx) => ({
  plugins: [
    require('postcss-css-reset'),
    require('postcss-mixins'),
    require('postcss-simple-vars'),
    require('postcss-color-function'),
    require('postcss-nested'),
    require('postcss-custom-media'),
    require('postcss-media-minmax')
  ]
});
```

**この順番通りにプラグインが読み込まれるので、順番は非常に重要です。**


# HUGOで条件分岐をする
PostCSSの設定が終わったので、HUGOに戻ります。

#### トップページのタイトルを変える

トップページはタイトルを変えたいので、条件分岐を設定していきます。

```html:
<title>{{if .IsHome}}{{ .Site.Params.homeTitle }}{{ else }}{{ .Title }}{{ end }} | {{ .Site.Params.SiteName }}</title>
```

これで、トップページの際は```config.tomlで設定した、homeTitle```が呼び出されて、それ以外のページは```HUGOのFrontMatterで設定したTitle```が呼び出されます。

#### その他の条件分岐も設定していきます

```html:
<meta name="keywords" content="{{ with .Params.Keyword }}{{ . }}{{ else }}{{ .Site.Params.Keyword }}{{ end }}">
<meta name="description" content="{{ with .Params.Description }}{{ . }}{{ else }}{{ .Site.Params.Description }}{{ end }}">
<meta property="og:title" content="{{if .IsHome}}{{ .Site.Params.homeTitle }}{{ else }}{{ .Title }}{{ end }} | {{ .Site.Params.SiteName }}">
<meta property="og:description" content="{{ with .Params.excerpt }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
<meta property="og:url" content="{{ .Permalink }}">
<meta property="og:image" content="{{ .Site.BaseURL }}{{ with .Params.ogp }}{{ . }}{{ else }}images/ogp.jpg{{ end }}">
<meta property="og:type" content="{{if .IsHome}}website{{ else }}article{{ end }}">
<meta property="og:site_name" content="{{ .Site.Params.SiteName }}">
<meta property="og:locale" content="{{ .Site.Params.LanguageCode }}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{if .IsHome}}{{ .Site.Params.homeTitle }}{{ else }}{{ .Title }}{{ end }} | {{ .Site.Params.SiteName }}">
<meta name="twitter:description" content="{{ with .Params.excerpt }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
<meta name="twitter:image:src" content="{{ .Site.BaseURL }}{{ with .Params.ogp }}{{ . }}{{ else }}images/twitter_ogp.jpg{{ end }}">
<meta name="twitter:domain" content="{{ .Site.Params.Domain }}">
```

```html:
<meta name="keywords" content="{{ with .Params.Keyword }}{{ . }}{{ else }}{{ .Site.Params.Keyword }}{{ end }}">
```

上記の条件分岐の場合、HUGOの[FrontMatter](https://gohugo.io/content-management/front-matter/)内に```keyword```があれば呼び出し、ない場合は```config.tomlで設定した、Keyword```が呼び出されます。

```markdown:
---
title: "title"
keyword: "keywordが入ります"
date: 2018-04-05
categories: ['photos']
caption: "Photoshop"
eyecatch: '/images/photos/photo01_firstview_ja.png'
ogp: 'images/photos/photo01_firstview.png'
url: '/photos/photo01/'
draft: false
---
```

上記の場合は、```keywordが入ります```が呼び出され、もしない場合は```config.tomlで設定した、Keyword```が呼び出されます。



# 部品をコンポーネント化していく
HUGOの[Partial](https://gohugo.io/templates/partials/)を使って、繰り返し使いそうな部品はコンポーネント化しておきます。

```html:
<!DOCTYPE html>
<html lang="{{ .Site.Params.LanguageCode }}">
<head>
  <meta charset="UTF-8">
  <title>{{if .IsHome}}{{ .Site.Params.homeTitle }}{{ else }}{{ .Title }}{{ end }} | {{ .Site.Params.SiteName }}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
  <meta name="keywords" content="{{ with .Params.Keyword }}{{ . }}{{ else }}{{ .Site.Params.Keyword }}{{ end }}">
  <meta name="description" content="{{ with .Params.Description }}{{ . }}{{ else }}{{ .Site.Params.Description }}{{ end }}">
  {{ partial "ogp.html" . }}
  {{ partial "styles.html" . }}
</head>
```


# 多言語対応していく
作っていく途中で多言語対応をしたくなったので、やってみました。
これが、HUGOの目玉機能の一つです。

参考にしたポートフォリオは多言語対応していなかったので、Githubを検索しまくって、参考になりそうなRepositoryを探します。

#### SkycoinのRepositoryを真似る
[Skycoinのブログ](https://www.skycoin.net/blog/)が、一番機能的に豊富で、わかりやすい構成になっていたので、
[SkycoinのブログのRepository](https://github.com/skycoin/blog)を真似ることにしました。



# Netlifyに対応させていく
最初は[Github Pages](https://pages.github.com/)を使ってサイトを公開していましたが、以下の理由から、Netlifyに乗り換えました。

- 独自ドメインのSSL対応が大変
- 毎回buildコマンドを打つのが面倒になった
- Formを使ってみたくなった

Formは、Netlifymが提供している[Netlify Forms](https://www.netlify.com/docs/form-handling/)を使って、簡単に導入することができました。

```html:
<div class="form__wrapper">
  <form name="contact" netlify-honeypot="bot-field" action="thank-you" method="post" netlify="true">
  <input type="hidden" name="form-name" value="contact" />
    <h2>{{ i18n "formTitle" }}</h2>
    <div class="field__container">
      <div class="field__contents">
        <div class="mc-field-group field__input">
          <div class="form__label__wrapper">
            <figure class="gopher__wrapper"><img src="{{ .Site.BaseURL }}images/icon/gopher_h.svg" alt=""></figure>
            <label class="form__label">{{ i18n "formName" }}<span class="span__required">{{ i18n "formRequired" }}</span></label>
          </div>
          <input type="text" name="NAME" required>
        </div>
        <div class="mc-field-group field__input">
          <div class="form__label__wrapper">
            <figure class="gopher__wrapper"><img src="{{ .Site.BaseURL }}images/icon/gopher_u.svg" alt=""></figure>
            <label class="form__label">{{ i18n "formMailaddress" }}<span class="span__required">{{ i18n "formRequired" }}</span></label>
          </div>
          <input type="email" name="EMAIL" required>
        </div>
      </div>
      <div class="field__contents">
        <div class="mc-field-group field__textarea">
          <div class="form__label__wrapper">
            <figure class="gopher__wrapper"><img src="{{ .Site.BaseURL }}images/icon/gopher_o.svg" alt=""></figure>
            <figure class="gopher__wrapper"><img src="{{ .Site.BaseURL }}images/icon/gopher_g.svg" alt=""></figure>
            <label class="form__label">{{ i18n "formInquiry" }}<span class="span__required">{{ i18n "formRequired" }}</span></label>
          </div>
          <textarea name="Message" cols="30" rows="10" required></textarea>
        </div>
      </div>
    </div>
    <div class="form__button__wrapper">
      <button class="submit__button" type="submit">{{ i18n "formSubmit" }}</button>
    </div>
  </form>
</div>
```


# まとめ
HUGOはシンプルだが多機能、かつ動作も速く、
PostCSSは設定が大変だが、これもNode製だから動作も早く、プラグインを組み合わせて自分だけのCSSプリプロセッサーを作ることができます。
Netlifyは、ただのホスティングサービスだけでなく、静的サイトジェネレーターの弱点であるFormまで補ってくれる、とても素晴らしいサービスでした。