<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Hugo - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Hugo - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 16:30:59 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/hugo/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ From WordPress to Hugo – How I Migrated a 250+ Page Site and the Scripts I Used ]]>
                </title>
                <description>
                    <![CDATA[ I recently decided to switch Boot.dev's blog from WordPress to a static site generator. I decided on Hugo, partly because I’m a huge fan of the Go programming language, but also because it just seemed like the best option from a dev-experience perspe... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/wordpress-to-hugo-scripts/</link>
                <guid isPermaLink="false">66b9e9f3fbdfcf519c5e1a68</guid>
                
                    <category>
                        <![CDATA[ Hugo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Static Site Generators ]]>
                    </category>
                
                    <category>
                        <![CDATA[ WordPress ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Lane Wagner ]]>
                </dc:creator>
                <pubDate>Tue, 30 Aug 2022 23:25:47 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/08/1__D5sbQUd4XwTy9yt67BCCA-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I recently decided to switch <a target="_blank" href="https://boot.dev">Boot.dev's</a> blog from WordPress to a static site generator.</p>
<p>I decided on <a target="_blank" href="https://gohugo.io/">Hugo</a>, partly because I’m a huge fan of the Go programming language, but also because it just seemed like the best option from a dev-experience perspective. </p>
<p>Here’s why I decided to move away from WordPress:</p>
<ul>
<li>I wanted to write and store all my posts in Markdown.</li>
<li>I wanted to version control all my posts in Git/Github.</li>
<li>I wanted to be able to use ctrl+shift+f to find and edit the content on my blog.</li>
<li>I wanted a less buggy way to do “global blocks”</li>
<li>I didn’t want to spend hours fine-tuning performance. My posts are just text and images, so page speed scores should be 100 by default!</li>
<li>I wanted to use straight CSS for styling, making it easier for my blog and app to have identical styling.</li>
<li>I wanted to host the site for free</li>
</ul>
<p>With all those goals in mind, Hugo was a perfect choice. I’ve been super happy since making the switch, but there were some hiccups along the way. </p>
<p>WordPress takes care of some of the nuances of SEO-tuning for you, and if you forget to redo them yourself, you’ll end up sacrificing rankings.</p>
<h2 id="heading-how-to-export-wordpress-posts-as-markdown">How to Export WordPress Posts as Markdown</h2>
<p>Manually copying and pasting all of your posts out of the WordPress GUI into Markdown files could take a <em>very</em> long time. Especially if you have hundreds of posts like I do. </p>
<p>In order to speed up the process, I used <a target="_blank" href="https://wordpress.org/plugins/wp-gatsby-markdown-exporter/">this Markdown plugin</a> to export all of my posts as markdown at once. It’s called “Gatsby Markdown Exporter”, but it works for Hugo just as well.</p>
<p>I’m not going to go over the basics of getting up and running with Hugo, as I’m going to assume you are already familiar with it. But if you’re not, you can read the <a target="_blank" href="https://gohugo.io/getting-started/quick-start/">quick start guide here</a>.</p>
<h2 id="heading-how-to-use-hugos-built-in-seo-templates">How to Use Hugo’s Built-in SEO Templates</h2>
<p>WordPress and its various SEO plugins take care of a lot of SEO-related hocus-pocus for you. So when you move to a static site generator you often have to do some of that stuff yourself.</p>
<p>As it turns out, Hugo has some turn-key solutions for most of this, so using the right <a target="_blank" href="https://gohugo.io/templates/internal/">internal templates</a> can get you all the open-graph, schema, and Twitter meta tags out of the box.</p>
<p>For me, that meant simply adding these 3 lines to my <code>layouts/partials/header.html</code> file within the <code>&lt;head&gt;</code> tag:</p>
<pre><code class="lang-html">{{ template "_internal/opengraph.html" . }}
{{ template "_internal/twitter_cards.html" . }}
{{ template "_internal/schema.html" . }}
</code></pre>
<p>Now we'll get into the scripts I wrote to help me with this process.</p>
<h2 id="heading-script-1-checking-for-broken-links">Script 1 — Checking for Broken Links</h2>
<p>You can solve a lot of your WordPress woes by installing plugins for various tasks. Well, assuming those plugins don’t break your entire WP installation.</p>
<p>With Hugo, I just write little text-manipulation or HTTP scripts to do my bidding.</p>
<p>This first script is just a simple iterator that crawls the site and reports any broken links. You can see the <a target="_blank" href="https://github.com/bootdotdev/blog/blob/main/scripts/linkcheck/main.go">full source code here</a>. It's small edit of the script the Go team uses to check for broken links on the Go programming language's website.</p>
<h2 id="heading-script-2-minifying-images">Script 2 — Minifying Images</h2>
<p>When working with WordPress, you would typically upload a blog post image, and some SEO plugin would optimize that image’s size for you. </p>
<p>In Hugo, no such optimizations are made for you. It simply serves the exact image you add to your “static” folder.</p>
<p>I wrote a little script to optimize my images. It does the following:</p>
<ul>
<li>Takes an input image of nearly any type (.png, .jpeg, .gif, etc).</li>
<li>Converts it to the <code>.webp</code> format (a performant format).</li>
<li>Shrinks the image to a max image size I've configured if it’s too large.</li>
</ul>
<p>This script is written in Node.js and <a target="_blank" href="https://github.com/bootdotdev/blog/blob/main/scripts/image-min.js">the source code can be found here</a>.</p>
<h2 id="heading-script-3-managing-global-shortcodes">Script 3 — Managing Global Shortcodes</h2>
<p>WordPress had a feature called “global blocks”. They're super useful for things like newsletter signup boxes that show up in the middle of your articles, but that you want to be able to update in a single place. </p>
<p>In Hugo, you can use <a target="_blank" href="https://gohugo.io/content-management/shortcodes/">shortcodes</a> to achieve a similar purpose.</p>
<p>Unfortunately, it’s the inserting and removing of the shortcodes <em>themselves</em> that actually becomes tedious at scale. </p>
<p>For example, let’s say I added my newsletter signup shortcode after the 5th paragraph in all my articles, but now I want it to come after the 7th paragraph. I have to go manually copy/paste the short code!</p>
<p>Well, not anymore. Again, this is the great thing about storing all your blog posts in a text format — we can script some simple solutions. I wrote two scripts, and their source code can be found here:</p>
<ul>
<li><a target="_blank" href="https://github.com/bootdotdev/blog/blob/main/scripts/rmshorts/main.go">rmshorts</a></li>
<li><a target="_blank" href="https://github.com/bootdotdev/blog/blob/main/scripts/addshorts/main.go">addshorts</a></li>
</ul>
<p>These two scripts allow me move my global blocks with ease. For example, to remove all instances of <code>myshortcode</code> from the blog:</p>
<pre><code class="lang-bash">rmshorts myshortcode
</code></pre>
<p>Then to add <code>myshortcode</code> as its own paragraph after the 2nd section of every blog post across the site:</p>
<pre><code class="lang-bash">addshorts myshortcode 2
</code></pre>
<p>This has been a huge time saver.</p>
<h2 id="heading-script-4-converting-docx-to-markdown">Script 4 — Converting .docx to Markdown</h2>
<p>Last but not least, I work with some non-technical writers. My writers aren’t used to writing Markdown. Google docs is their tool of choice. </p>
<p>I have no problem with that, as I want them to be effective. My solution was to write a <a target="_blank" href="https://github.com/bootdotdev/blog/blob/main/scripts/docxmd.sh">little bash script</a> that uses <a target="_blank" href="https://pandoc.org/">pandoc</a> under the hood to convert the Google Docs to Markdown files.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>I hope some of these scripts are valuable to you, and I really can’t recommend enough making the switch to a static site generator. If you’re willing to put in a few hours to get a great working environment up and running for your blog it will save you a lot of time and money in the long run.</p>
<p>Remember, always automate the boring stuff!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Hugo vs Jekyll: an Epic Battle of Static Site Generator Themes ]]>
                </title>
                <description>
                    <![CDATA[ In this article, we'll compare the nuances of creating themes for the top two static site generators. I recently took on the task of creating a documentation site theme for two projects. Both projects needed the same basic features, but one uses Jeky... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/hugo-vs-jekyll-battle-of-static-site-generator-themes/</link>
                <guid isPermaLink="false">66bd8f5f27629f4c5e1893b2</guid>
                
                    <category>
                        <![CDATA[ Hugo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JAMstack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ jekyll ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Static Site Generators ]]>
                    </category>
                
                    <category>
                        <![CDATA[ themes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ website development, ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Victoria Drake ]]>
                </dc:creator>
                <pubDate>Mon, 27 Apr 2020 13:42:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/04/cover-2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, we'll compare the nuances of creating themes for the top two static site generators.</p>
<p>I recently took on the task of creating a documentation site theme for two projects. Both projects needed the same basic features, but one uses Jekyll while the other uses Hugo.</p>
<p>In typical developer rationality, there was clearly only one option. I decided to create the same theme in both frameworks, and to give you, dear reader, a side-by-side comparison.</p>
<p>This post isn’t a comprehensive theme-building guide, but is rather intended to familiarize you with the process of building a theme in either generator. Here's what we'll cover:</p>
<ul>
<li>How theme files are organized</li>
<li>Where to put content</li>
<li>How templating works</li>
<li>Creating a top-level menu with the <code>pages</code> object</li>
<li>Creating a menu with nested links from a data list</li>
<li>Putting the template together</li>
<li>Creating styles</li>
<li>How to configure and deploy to GitHub Pages</li>
</ul>
<p>Here’s a crappy wireframe of the theme I’m going to create.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/wireframe.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you’re planning to build-along, it may be helpful to serve the theme locally as you build it – and both generators offer this functionality. For Jekyll, run <code>jekyll serve</code>, and for Hugo, <code>hugo serve</code>.</p>
<p>There are two main elements: the main content area, and the all-important sidebar menu. To create them, you’ll need template files that tell the site generator how to generate the HTML page. To organize theme template files in a sensible way, you first need to know what  directory structure the site generator expects.</p>
<h2 id="heading-how-theme-files-are-organized">How theme files are organized</h2>
<p>Jekyll supports gem-based themes, which users can install like any other Ruby gems. This method hides theme files in the gem, so for the purposes of this comparison, we aren’t using gem-based themes.</p>
<p>When you run <code>jekyll new-theme &lt;name&gt;</code>, Jekyll will scaffold a new theme for you. Here’s what those files look like:</p>
<pre><code class="lang-sh">.
├── assets
├── Gemfile
├── _includes
├── _layouts
│   ├── default.html
│   ├── page.html
│   └── post.html
├── LICENSE.txt
├── README.md
├── _sass
└── &lt;name&gt;.gemspec
</code></pre>
<p>The directory names are appropriately descriptive. The <code>_includes</code> directory is for small bits of code that you reuse in different places,  in much the same way you’d put butter on everything. (Just me?) </p>
<p>The <code>_layouts</code> directory contains templates for different types of pages on your site. The <code>_sass</code> folder is for <a target="_blank" href="https://sass-lang.com/documentation/syntax">Sass</a> files used to build your site’s stylesheet.</p>
<p>You can scaffold a new Hugo theme by running <code>hugo new theme &lt;name&gt;</code>. It has these files:</p>
<pre><code class="lang-sh">.
├── archetypes
│   └── default.md
├── layouts
│   ├── 404.html
│   ├── _default
│   │   ├── baseof.html
│   │   ├── list.html
│   │   └── single.html
│   ├── index.html
│   └── partials
│       ├── footer.html
│       ├── header.html
│       └── head.html
├── LICENSE
├── static
│   ├── css
│   └── js
└── theme.toml
</code></pre>
<p>You can see some similarities. Hugo’s page template files are tucked into <code>layouts/</code>. Note that the <code>_default</code> page type has files for a <code>list.html</code> and a <code>single.html</code>. </p>
<p>Unlike Jekyll, Hugo uses these specific file names to distinguish between <a target="_blank" href="https://gohugo.io/templates/lists/">list pages</a> (like a page with links to all your blog posts on it) and <a target="_blank" href="https://gohugo.io/templates/single-page-templates/">single pages</a> (like one of your blog posts). The <code>layouts/partials/</code> directory contains the buttery reusable bits, and stylesheet files have a spot picked out in <code>static/css/</code>.</p>
<p>These directory structures aren’t set in stone, as both site generators allow some measure of customization. For example, Jekyll lets  you define <a target="_blank" href="https://jekyllrb.com/docs/collections/">collections</a>, and Hugo makes use of <a target="_blank" href="https://gohugo.io/content-management/page-bundles/">page bundles</a>. These features let you organize your content multiple ways, but for now, let's look at where to put some simple pages.</p>
<h2 id="heading-where-to-put-content">Where to put content</h2>
<p>To create a site menu that looks like this:</p>
<pre><code class="lang-md">Introduction
<span class="hljs-code">    Getting Started
    Configuration
    Deploying
Advanced Usage
    All Configuration Settings
    Customizing
    Help and Support</span>
</code></pre>
<p>You’ll need two sections (“Introduction” and “Advanced Usage”) containing their respective subsections.</p>
<p>Jekyll isn’t strict with its content location. It expects pages in the root of your site, and will build whatever’s there. Here’s how you might organize these pages in your Jekyll site root:</p>
<pre><code class="lang-sh">.
├── 404.html
├── assets
├── Gemfile
├── _includes
├── index.markdown
├── intro
│   ├── config.md
│   ├── deploy.md
│   ├── index.md
│   └── quickstart.md
├── _layouts
│   ├── default.html
│   ├── page.html
│   └── post.html
├── LICENSE.txt
├── README.md
├── _sass
├── &lt;name&gt;.gemspec
└── usage
    ├── customizing.md
    ├── index.md
    ├── settings.md
    └── support.md
</code></pre>
<p>You can change the location of the site source in your <a target="_blank" href="https://jekyllrb.com/docs/configuration/default/">Jekyll configuration</a>.</p>
<p>In Hugo, all rendered content is expected in the <code>content/</code> folder. This prevents Hugo from trying to render pages you don’t want, such as <code>404.html</code>, as site content. Here’s how you might organize your <code>content/</code> directory in Hugo:</p>
<pre><code class="lang-sh">.
├── _index.md
├── intro
│   ├── config.md
│   ├── deploy.md
│   ├── _index.md
│   └── quickstart.md
└── usage
    ├── customizing.md
    ├── _index.md
    ├── settings.md
    └── support.md
</code></pre>
<p>To Hugo, <code>_index.md</code> and <code>index.md</code> mean different things. It can be helpful to know what kind of <a target="_blank" href="https://gohugo.io/content-management/page-bundles/">Page Bundle</a> you want for each section: Leaf, which has no children, or Branch.</p>
<p>Now that you have some idea of where to put things, let’s look at how to build a page template.</p>
<h2 id="heading-how-templating-works">How templating works</h2>
<p>Jekyll page templates are built with the <a target="_blank" href="https://jekyllrb.com/docs/liquid/">Liquid templating language</a>. It uses braces to output variable content to a page, such as the page’s title: <code>{{ page.title }}</code>.</p>
<p>Hugo’s templates also use braces, but they’re built with <a target="_blank" href="https://gohugo.io/templates/introduction/">Go Templates</a>. The syntax is similar, but different: <code>{{ .Title }}</code>.</p>
<p>Both Liquid and Go Templates can handle logic. Liquid uses <em>tags</em> syntax to denote logic operations:</p>
<pre><code class="lang-liquid">{% if user %}
  Hello {{ user.name }}!
{% endif %}
</code></pre>
<p>And Go Templates places its functions and arguments in its braces syntax:</p>
<pre><code class="lang-go">{{ <span class="hljs-keyword">if</span> .User }}
    Hello {{ .User }}!
{{ end }}
</code></pre>
<p>Templating languages allow you to build one  skeleton HTML page, then tell the site generator to put variable content in areas you define. Let’s compare two possible <code>default</code> page templates for Jekyll and Hugo.</p>
<p>Jekyll’s scaffold <code>default</code> theme is bare, so we’ll look at their starter theme <a target="_blank" href="https://github.com/jekyll/minima">Minima</a>. Here’s <code>_layouts/default.html</code> in Jekyll (Liquid):</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"{{ page.lang | default: site.lang | default: "</span><span class="hljs-attr">en</span>" }}"&gt;</span>

  {%- include head.html -%}

  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>

    {%- include header.html -%}

    <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-content"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Content"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"wrapper"</span>&gt;</span>
        {{ content }}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

    {%- include footer.html -%}

  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Here’s Hugo’s scaffold theme <code>layouts/_default/baseof.html</code> (Go Templates):</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    {{- partial "head.html" . -}}
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        {{- partial "header.html" . -}}
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"content"</span>&gt;</span>
        {{- block "main" . }}{{- end }}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        {{- partial "footer.html" . -}}
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Different syntax, same idea. Both templates pull in reusable bits for <code>head.html</code>, <code>header.html</code>, and <code>footer.html</code>.  These show up on a lot of pages, so it makes sense not to have to  repeat yourself. </p>
<p>Both templates also have a spot for the main content, though the Jekyll template uses a variable (<code>{{ content }}</code>) while Hugo uses a block (<code>{{- block "main" . }}{{- end }}</code>). <a target="_blank" href="https://gohugo.io/templates/base/#readout">Blocks</a> are just another way Hugo lets you define reusable bits.</p>
<p>Now that you know how templating works, you can build the sidebar menu for the theme.</p>
<h2 id="heading-creating-a-top-level-menu-with-the-pages-object">Creating a top-level menu with the <code>pages</code> object</h2>
<p>You can programmatically create a top-level menu from your pages. It will look like this:</p>
<pre><code class="lang-md">Introduction
Advanced Usage
</code></pre>
<p>Let’s start with Jekyll. You can display links to site pages in your Liquid template by iterating through the <code>site.pages</code> object that Jekyll provides and building a list:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    {% for page in site.pages %}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ page.url | absolute_url }}"</span>&gt;</span>{{ page.title }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    {% endfor %}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p>This returns all of the site’s pages, including all the ones that you might not want, like <code>404.html</code>. You can filter for the pages you actually want with a couple more tags, such as conditionally including pages if they have a <code>section: true</code> parameter set:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    {% for page in site.pages %}
    {%- if page.section -%}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ page.url | absolute_url }}"</span>&gt;</span>{{ page.title }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    {%- endif -%}
    {% endfor %}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p>You can achieve the same effect with slightly less code in Hugo. Loop through Hugo’s <code>.Pages</code> object using Go Template’s <a target="_blank" href="https://golang.org/pkg/text/template/#hdr-Actions"><code>range</code> action</a>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
{{ range .Pages }}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{.Permalink}}"</span>&gt;</span>{{.Title}}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
{{ end }}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p>This template uses the <code>.Pages</code> object to return all the top-level pages in <code>content/</code> of your Hugo site. Since Hugo uses a specific folder for the site content you want rendered, there’s no additional filtering necessary to build a simple menu of site pages.</p>
<h2 id="heading-creating-a-menu-with-nested-links-from-a-data-list">Creating a menu with nested links from a data list</h2>
<p>Both site generators can use a separately defined data list of links to render a menu in your template. This is more suitable for creating nested links, like this:</p>
<pre><code class="lang-md">Introduction
<span class="hljs-code">    Getting Started
    Configuration
    Deploying
Advanced Usage
    All Configuration Settings
    Customizing
    Help and Support</span>
</code></pre>
<p>Jekyll supports <a target="_blank" href="https://jekyllrb.com/docs/datafiles/">data files</a> in a few formats, including YAML. Here’s the definition for the menu above in <code>_data/menu.yml</code>:</p>
<pre><code class="lang-yml"><span class="hljs-attr">section:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">page:</span> <span class="hljs-string">Introduction</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">/intro</span>
    <span class="hljs-attr">subsection:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">page:</span> <span class="hljs-string">Getting</span> <span class="hljs-string">Started</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">/intro/quickstart</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">page:</span> <span class="hljs-string">Configuration</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">/intro/config</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">page:</span> <span class="hljs-string">Deploying</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">/intro/deploy</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">page:</span> <span class="hljs-string">Advanced</span> <span class="hljs-string">Usage</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">/usage</span>
    <span class="hljs-attr">subsection:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">page:</span> <span class="hljs-string">Customizing</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">/usage/customizing</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">page:</span> <span class="hljs-string">All</span> <span class="hljs-string">Configuration</span> <span class="hljs-string">Settings</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">/usage/settings</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">page:</span> <span class="hljs-string">Help</span> <span class="hljs-string">and</span> <span class="hljs-string">Support</span>
        <span class="hljs-attr">url:</span> <span class="hljs-string">/usage/support</span>
</code></pre>
<p>Here’s how to render the data in the sidebar template:</p>
<pre><code class="lang-html">{% for a in site.data.menu.section %}
<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ a.url }}"</span>&gt;</span>{{ a.page }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    {% for b in a.subsection %}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ b.url }}"</span>&gt;</span>{{ b.page }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    {% endfor %}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
{% endfor %}
</code></pre>
<p>This method allows you to build a custom menu, two nesting levels deep. The nesting levels are limited by the <code>for</code> loops in the template. For a recursive version that handles further levels of nesting, see <a target="_blank" href="https://jekyllrb.com/tutorials/navigation/#scenario-9-nested-tree-navigation-with-recursion">Nested tree navigation with recursion</a>.</p>
<p>Hugo does something similar with its <a target="_blank" href="https://gohugo.io/templates/menu-templates/#section-menu-for-lazy-bloggers">menu templates</a>. You can define menu links in your <a target="_blank" href="https://gohugo.io/getting-started/configuration/">Hugo site config</a>, and even add useful properties that Hugo understands, like weighting. Here’s a definition of the menu above in <code>config.yaml</code>:</p>
<pre><code class="lang-yml"><span class="hljs-attr">sectionPagesMenu:</span> <span class="hljs-string">main</span>

<span class="hljs-attr">menu:</span>  
  <span class="hljs-attr">main:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">identifier:</span> <span class="hljs-string">intro</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">Introduction</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/intro/</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Getting</span> <span class="hljs-string">Started</span>
      <span class="hljs-attr">parent:</span> <span class="hljs-string">intro</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/intro/quickstart/</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configuration</span>
      <span class="hljs-attr">parent:</span> <span class="hljs-string">intro</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/intro/config/</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">2</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploying</span>
      <span class="hljs-attr">parent:</span> <span class="hljs-string">intro</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/intro/deploy/</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">3</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">identifier:</span> <span class="hljs-string">usage</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">Advanced</span> <span class="hljs-string">Usage</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/usage/</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Customizing</span>
      <span class="hljs-attr">parent:</span> <span class="hljs-string">usage</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/usage/customizing/</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">2</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">All</span> <span class="hljs-string">Configuration</span> <span class="hljs-string">Settings</span>
      <span class="hljs-attr">parent:</span> <span class="hljs-string">usage</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/usage/settings/</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Help</span> <span class="hljs-string">and</span> <span class="hljs-string">Support</span>
      <span class="hljs-attr">parent:</span> <span class="hljs-string">usage</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/usage/support/</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">3</span>
</code></pre>
<p>Hugo uses the <code>identifier</code>, which must match the section name, along with the <code>parent</code> variable to handle nesting. Here’s how to render the menu in the sidebar template:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    {{ range .Site.Menus.main }}
    {{ if .HasChildren }}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ .URL }}"</span>&gt;</span>{{ .Name }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"sub-menu"</span>&gt;</span>
        {{ range .Children }}
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ .URL }}"</span>&gt;</span>{{ .Name }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        {{ end }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    {{ else }}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ .URL }}"</span>&gt;</span>{{ .Name }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    {{ end }}
    {{ end }}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p>The <code>range</code> function iterates over the menu data, and Hugo’s <code>.Children</code> variable handles nested pages for you.</p>
<h2 id="heading-putting-the-template-together">Putting the template together</h2>
<p>With your menu in your reusable sidebar bit (<code>_includes/sidebar.html</code> for Jekyll and <code>partials/sidebar.html</code> for Hugo), you can add it to the <code>default.html</code> template.</p>
<p>In Jekyll:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"{{ page.lang | default: site.lang | default: "</span><span class="hljs-attr">en</span>" }}"&gt;</span>

{%- include head.html -%}

<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    {%- include sidebar.html -%}

    {%- include header.html -%}

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"content"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-content"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Content"</span>&gt;</span>
        {{ content }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    {%- include footer.html -%}

<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>In Hugo:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
{{- partial "head.html" . -}}

<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    {{- partial "sidebar.html" . -}}

    {{- partial "header.html" . -}}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"content"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-content"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Content"</span>&gt;</span>
        {{- block "main" . }}{{- end }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    {{- partial "footer.html" . -}}
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>When the site is generated, each page will contain all the code from your <code>sidebar.html</code>.</p>
<h2 id="heading-create-a-stylesheet">Create a stylesheet</h2>
<p>Both site generators accept Sass for creating CSS stylesheets. Jekyll <a target="_blank" href="https://jekyllrb.com/docs/assets/">has Sass processing built in</a>, and Hugo uses <a target="_blank" href="https://gohugo.io/hugo-pipes/scss-sass/">Hugo Pipes</a>. Both options have some quirks.</p>
<h3 id="heading-sass-and-css-in-jekyll">Sass and CSS in Jekyll</h3>
<p>To process a Sass file in Jekyll, create your style definitions in the <code>_sass</code> directory. For example, in a file at <code>_sass/style-definitions.scss</code>:</p>
<pre><code class="lang-scss"><span class="hljs-variable">$background-color</span>: <span class="hljs-number">#eef</span> !default;
<span class="hljs-variable">$text-color</span>: <span class="hljs-number">#111</span> !default;

<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-variable">$background-color</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-variable">$text-color</span>;
}
</code></pre>
<p>Jekyll won’t generate this file directly, as it only processes files with front matter. To create the end-result  filepath for your site’s stylesheet, use a placeholder with empty front matter where you want the <code>.css</code> file to appear. For example, <code>assets/css/style.scss</code>. In this file, simply import your styles:</p>
<pre><code class="lang-scss">---
---

<span class="hljs-keyword">@import</span> <span class="hljs-string">"style-definitions"</span>;
</code></pre>
<p>This rather hackish configuration has an upside: you can use Liquid template tags and variables in your placeholder file. This is a nice way to allow users to set variables from the site <code>_config.yml</code>, for example.</p>
<p>The resulting CSS stylesheet in your generated site has the path <code>/assets/css/style.css</code>. You can link to it in your site’s <code>head.html</code> using:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ "</span>/<span class="hljs-attr">assets</span>/<span class="hljs-attr">css</span>/<span class="hljs-attr">style.css</span>" | <span class="hljs-attr">relative_url</span> }}" <span class="hljs-attr">media</span>=<span class="hljs-string">"screen"</span>&gt;</span>
</code></pre>
<h3 id="heading-sass-and-hugo-pipes-in-hugo">Sass and Hugo Pipes in Hugo</h3>
<p>Hugo uses <a target="_blank" href="https://gohugo.io/hugo-pipes/scss-sass/">Hugo Pipes</a> to process Sass to CSS. You can achieve this by using Hugo’s asset processing function, <code>resources.ToCSS</code>, which expects a source in the <code>assets/</code> directory. It takes the SCSS file as an argument. </p>
<p>With your style definitions in a Sass file at <code>assets/sass/style.scss</code>, here’s how to get, process, and link your Sass in your theme’s <code>head.html</code>:</p>
<pre><code class="lang-html">{{ $style := resources.Get "/sass/style.scss" | resources.ToCSS }}
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ $style.RelPermalink }}"</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"screen"</span>&gt;</span>
</code></pre>
<p>Hugo asset processing <a target="_blank" href="https://gohugo.io/troubleshooting/faq/#i-get-tocss--this-feature-is-not-available-in-your-current-hugo-version">requires extended Hugo</a>, which you may not have by default. You can get extended Hugo from the <a target="_blank" href="https://github.com/gohugoio/hugo/releases">releases page</a>.</p>
<h2 id="heading-configure-and-deploy-to-github-pages">Configure and deploy to GitHub Pages</h2>
<p>Before your site generator can build your site, it needs a  configuration file to set some necessary parameters. Configuration files  live in the site root directory. Among other settings, you can declare  the name of the theme to use when building the site.</p>
<h3 id="heading-configure-jekyll">Configure Jekyll</h3>
<p>Here’s a minimal <code>_config.yml</code> for Jekyll:</p>
<pre><code class="lang-yml"><span class="hljs-attr">title:</span> <span class="hljs-string">Your</span> <span class="hljs-string">awesome</span> <span class="hljs-string">title</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">&gt;-</span> <span class="hljs-comment"># this means to ignore newlines until "baseurl:"</span>
  <span class="hljs-string">Write</span> <span class="hljs-string">an</span> <span class="hljs-string">awesome</span> <span class="hljs-string">description</span> <span class="hljs-string">for</span> <span class="hljs-string">your</span> <span class="hljs-string">new</span> <span class="hljs-string">site</span> <span class="hljs-string">here.</span> <span class="hljs-string">You</span> <span class="hljs-string">can</span> <span class="hljs-string">edit</span> <span class="hljs-string">this</span>
  <span class="hljs-string">line</span> <span class="hljs-string">in</span> <span class="hljs-string">_config.yml.</span> <span class="hljs-string">It</span> <span class="hljs-string">will</span> <span class="hljs-string">appear</span> <span class="hljs-string">in</span> <span class="hljs-string">your</span> <span class="hljs-string">document</span> <span class="hljs-string">head</span> <span class="hljs-string">meta</span> <span class="hljs-string">(for</span>
  <span class="hljs-string">Google</span> <span class="hljs-string">search</span> <span class="hljs-string">results)</span> <span class="hljs-string">and</span> <span class="hljs-string">in</span> <span class="hljs-string">your</span> <span class="hljs-string">feed.xml</span> <span class="hljs-string">site</span> <span class="hljs-string">description.</span>
<span class="hljs-attr">baseurl:</span> <span class="hljs-string">""</span> <span class="hljs-comment"># the subpath of your site, e.g. /blog</span>
<span class="hljs-attr">url:</span> <span class="hljs-string">""</span> <span class="hljs-comment"># the base hostname &amp; protocol for your site, e.g. http://example.com</span>
<span class="hljs-attr">theme:</span> <span class="hljs-comment"># for gem-based themes</span>
<span class="hljs-attr">remote_theme:</span> <span class="hljs-comment"># for themes hosted on GitHub, when used with GitHub Pages</span>
</code></pre>
<p>With <code>remote_theme</code>, any <a target="_blank" href="https://help.github.com/en/github/working-with-github-pages/adding-a-theme-to-your-github-pages-site-using-jekyll#adding-a-jekyll-theme-in-your-sites-_configyml-file">Jekyll theme hosted on GitHub can be used</a> with sites hosted on GitHub Pages.</p>
<p>Jekyll has a <a target="_blank" href="https://jekyllrb.com/docs/configuration/default/">default configuration</a>, so any parameters added to your configuration file will override the defaults. Here are <a target="_blank" href="https://jekyllrb.com/docs/configuration/options/">additional configuration settings</a>.</p>
<h3 id="heading-configure-hugo">Configure Hugo</h3>
<p>Here’s a minimal example of Hugo’s <code>config.yml</code>:</p>
<pre><code class="lang-yml"><span class="hljs-attr">baseURL:</span> <span class="hljs-string">https://example.com/</span> <span class="hljs-comment"># The full domain your site will live at</span>
<span class="hljs-attr">languageCode:</span> <span class="hljs-string">en-us</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Hugo</span> <span class="hljs-string">Docs</span> <span class="hljs-string">Site</span>
<span class="hljs-attr">theme:</span> <span class="hljs-comment"># theme name</span>
</code></pre>
<p>Hugo makes no assumptions, so if a necessary parameter is missing, you’ll see a warning when building or serving your site. Here are <a target="_blank" href="https://gohugo.io/getting-started/configuration/#all-configuration-settings">all configuration settings for Hugo</a>.</p>
<h3 id="heading-deploy-to-github-pages">Deploy to GitHub Pages</h3>
<p>Both generators build your site with a command.</p>
<p>For Jekyll, use <code>jekyll build</code>. See <a target="_blank" href="https://jekyllrb.com/docs/configuration/options/#build-command-options">further build options here</a>.</p>
<p>For Hugo, use <code>hugo</code>. You can run <code>hugo help</code> or see <a target="_blank" href="https://gohugo.io/getting-started/usage/#test-installation">further build options here</a>.</p>
<p>You’ll have to choose the source for your GitHub Pages site. Once done, your site will update each time you push a new build. Of course, you can also automate your GitHub Pages build using GitHub Actions. Here’s one for <a target="_blank" href="https://github.com/victoriadrake/hugo-latest-cd">building and deploying with Hugo</a>, and one for <a target="_blank" href="https://github.com/victoriadrake/jekyll-cd">building and deploying Jekyll</a>.</p>
<h2 id="heading-showtime">Showtime!</h2>
<p>All the substantial differences between these two generators are under the hood. All the same, let’s take a look at the finished themes, in two color variations.</p>
<p>Here’s Hugo:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/ogd_hugo.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here's Jekyll:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/ogd_jekyll.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-wait-who-won">Wait who won?</h2>
<p>?</p>
<p>Both Hugo and Jekyll have their quirks and conveniences.</p>
<p>From this developer’s perspective, Jekyll is a workable choice for simple sites without complicated organizational needs. If you’re looking to render some one-page posts in an <a target="_blank" href="https://jekyllrb.com/docs/themes/">available theme</a> and host with GitHub Pages, Jekyll will get you up and running fairly quickly.</p>
<p>Personally, I use Hugo. I like the organizational capabilities of its Page Bundles, and it’s backed by a dedicated and conscientious team that really seems to strive to facilitate convenience for their users. This is evident in Hugo’s many functions, and handy tricks like <a target="_blank" href="https://gohugo.io/content-management/image-processing/">Image Processing</a> and <a target="_blank" href="https://gohugo.io/content-management/shortcodes/">Shortcodes</a>. They seem to release new fixes and versions about as often as I make a new cup of coffee - which, depending on your use case, may be fantastic, or annoying.</p>
<p>If you still can’t decide, don’t worry. The <a target="_blank" href="https://github.com/opengitdocs">OpenGitDocs documentation theme</a> I created is available for both Hugo and Jekyll. Start with one, switch later if you want. That’s the benefit of having options.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to self-host a Hugo web app ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff After hosting with Netlify for a few years, I decided to head back to self hosting. There are a few reasons for that, but the main reasoning was that I had more control over how things worked. In this post, I'll show you my workflow fo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/my-latest-self-hosted-hugo-workflow/</link>
                <guid isPermaLink="false">66d850624c5150361c4fdb2e</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ FreeBSD ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Hugo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ self hosting ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sun, 29 Mar 2020 18:10:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/Self-hosted-Hugo.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p>After hosting with Netlify for a few years, I decided to head back to self hosting. There are a few reasons for that, but the main reasoning was that I had more control over how things worked.</p>
<p>In this post, I'll show you my workflow for deploying my <a target="_blank" href="https://gohugo.io">Hugo</a> generated site (<a target="_blank" href="https://www.jaredwolff.com">www.jaredwolff.com</a>). </p>
<p>Instead of using what most people would go for, I'll be doing all of this using a FreeBSD Jails-based server. Plus I'll show you some tricks I've learned over the years on bulk image resizing and more.</p>
<p>Let's get to it.</p>
<h2 id="heading-where-to-host">Where to host?</h2>
<p>If you want to host your own service, you'll need a server. That's where a VPS provider like Digital Ocean or Vultr comes in. I've been a fan and have used Digital Ocean for a while now.</p>
<p>To set up a new server here are some steps:</p>
<ol>
<li>Login to Digital Ocean. If you don’t have Digital Ocean and would like to support this blog click <a target="_blank" href="https://m.do.co/c/9574d3846a29">here</a> to create an account.</li>
<li>Go to <code>Account Settings</code> -&gt; <code>Security</code> and make sure you have an SSH key setup.</li>
<li>Create a new FreeBSD droplet. Make sure you use the UFS version <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.41.21_AM.png" alt="Create Droplet" width="730" height="471" loading="lazy"> <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.43.21_AM.png" alt="Choose FreeBSD 12.1 UFS" width="730" height="471" loading="lazy"></li>
<li>Make sure you select the $5 a month plan. For simple installs, this is more than enough! <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.44.13_AM.png" alt="$5 Plan" width="730" height="471" loading="lazy"></li>
<li>Make sure your SSH key is selected <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.45.26_AM.png" alt="Select SSH key" width="730" height="465" loading="lazy"></li>
<li>Finally click that green <strong>Create Droplet</strong> button! <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.45.24_AM.png" alt="Create droplet" width="730" height="465" loading="lazy"></li>
<li>SSH in once you’re done: <code>ssh root@&lt;yourserverip&gt;</code></li>
</ol>
<h2 id="heading-setting-up-your-freebsd-server-with-bastille">Setting up your FreeBSD server with Bastille</h2>
<p><img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/bastille.png" alt="images/bastille.png" width="730" height="486" loading="lazy"></p>
<p>Up until recently, everything was running on a Docker based platform using <a target="_blank" href="https://github.com/exoframejs/exoframe">Exoframe</a>. It was easy and almost brainless. </p>
<p>The downside was that Docker takes up wayyyy too many resources. Plus managing files within a Docker container is as much or more work than hosting it natively. Oh, and have you checked how much space Docker has been using on your machine lately? On my development machine its was about 19GB of space. ?</p>
<p>So what's the alternative?</p>
<p>FreeBSD Jails using Bastille.</p>
<p>I've been playing with Bastille for a few months now. The more I use it, the more it makes 100% sense.</p>
<p>Bastille allows you to create (now) portable lightweight FreeBSD based jails. These jails are "containers" that have virtually no overhead. There's no daemon (the operating system is the "daemon"!). Plus, jails are secure compared to the can of worms that Docker is. Yes, you may have to compile and port some utilities. Most though are already supported in FreeBSD's package manager <code>pkg</code>.</p>
<p>In this section you'll learn how to get a jail running with <code>caddy</code> so you can securely host your site.</p>
<p>Let's keep the momentum going!</p>
<p>Once you get the IP address for your server, you should login:</p>
<p>    ssh root@123.456.789.10</p>
<p>You should get a MOTD message and an <code>sh</code> prompt. Woo!</p>
<p>    FreeBSD 12.1-RELEASE-p2 GENERIC</p>
<p>    Welcome to FreeBSD!
    ...</p>
<p>    #</p>
<p>Let's install a few important bits using <code>pkg</code> (FreeBSD's package manager):</p>
<p>    pkg install restic rsync bastille</p>
<p>We'll be using <code>restic</code> for backups, <code>rsync</code> for transferring files and <code>bastille</code> for jail setup.</p>
<p>You also have to set up some static routes in your <code>pf.conf</code>. Here's an example of mine:</p>
<pre><code class="lang-shell">ext_if="vtnet0"

# Caddy related
caddy_addr=10.10.2.20

set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo

table &lt;jails&gt; persist
nat on $ext_if from &lt;jails&gt; to any -&gt; $ext_if

# container routes
rdr pass inet proto tcp from any to port 80 -&gt; $caddy_addr port 8880
rdr pass inet proto tcp from any to port 443 -&gt; $caddy_addr port 4443

# Enable dynamic rdr (see below)
rdr-anchor "rdr/*"

block in all
pass out quick modulate state
antispoof for $ext_if inet
pass in inet proto tcp from any to any port ssh flags S/SA keep state
</code></pre>
<p>This is a standard <code>pf.conf</code> file for <code>bastille</code>.  Make sure you edit <code>caddy_addr</code> to the IP you chose.</p>
<p>Now let's start the firewall. You will get kicked out of your <code>ssh</code> session:</p>
<pre><code>sysrc pf_enable=<span class="hljs-string">"YES"</span>
service pf start
</code></pre><p>Then let's get some <code>bastille</code> configuration out of the way:</p>
<pre><code># set up bastille networking
sysrc cloned_interfaces+=lo1
sysrc ifconfig_lo1_name=<span class="hljs-string">"bastille0"</span>
service netif cloneup

# bootstrap the base jail and start bastille
bastille bootstrap <span class="hljs-number">12.1</span>-RELEASE update
sysrc bastille_enable=<span class="hljs-string">"YES"</span>
service bastille start
</code></pre><p>This will set up your networking, and fetch the latest default base jail you'll use later.</p>
<p>Next, let's set up the jail:</p>
<pre><code>bastille create caddy <span class="hljs-number">12.1</span>-STABLE <span class="hljs-number">10.10</span><span class="hljs-number">.2</span><span class="hljs-number">.20</span>
bastille start caddy
</code></pre><p>Then install <code>caddy</code></p>
<pre><code>#install the binary
fetch https:<span class="hljs-comment">//github.com/caddyserver/caddy/releases/download/v1.0.4/caddy_v1.0.4_freebsd_amd64.tar.gz</span>
tar xvf caddy_v1<span class="hljs-number">.0</span><span class="hljs-number">.4</span>_freebsd_amd64.tar.gz caddy
bastille cp caddy caddy /usr/local/bin/
rm caddy

#create the caddy user
bastille cmd caddy pw useradd caddy -m -s /usr/sbin/nologin

#install ca root file
bastille pkg caddy install ca_root_nss
</code></pre><p>When installing <code>ca_root_nss</code> , <code>pkg</code> will have to initialize. Accept the prompts. Once you're done here we'll move on to the next step!</p>
<p>Once installation is complete, we should also configure <code>caddy</code> to start on boot. The easiest way to do that is use this <code>rc.d</code> script:</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/sh</span>

<span class="hljs-comment"># $FreeBSD: head/net/caddy/files/caddy.in 452063 2017-10-14 12:58:24Z riggs $</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># PROVIDE: caddy</span>
<span class="hljs-comment"># REQUIRE: LOGIN</span>
<span class="hljs-comment"># KEYWORD: shutdown</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Add the following lines to /etc/rc.conf.local or /etc/rc.conf</span>
<span class="hljs-comment"># to enable this service:</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># caddy_enable (bool):    Set to NO by default.</span>
<span class="hljs-comment">#                Set it to YES to enable caddy.</span>
<span class="hljs-comment"># caddy_user (user):        Set user to run caddy.</span>
<span class="hljs-comment">#                Default is "caddy".</span>
<span class="hljs-comment"># caddy_group (group):    Set group to run caddy.</span>
<span class="hljs-comment">#                Default is "caddy".</span>
<span class="hljs-comment"># caddy_conf (path):        Path to caddy configuration file.</span>
<span class="hljs-comment">#                Default is /usr/local/etc/caddyfile.conf</span>

. /etc/rc.subr

name=caddy
rcvar=caddy_enable

load_rc_config <span class="hljs-variable">$name</span>

: <span class="hljs-variable">${caddy_enable:="NO"}</span>
: <span class="hljs-variable">${caddy_user:="caddy"}</span>
: <span class="hljs-variable">${caddy_group:="caddy"}</span>
: <span class="hljs-variable">${caddy_conf:="/usr/local/etc/caddyfile.conf"}</span>
: <span class="hljs-variable">${caddy_log:="/home/caddy/caddy.log"}</span>
: <span class="hljs-variable">${caddy_env:="CADDYPATH=/home/caddy/"}</span>
: <span class="hljs-variable">${caddy_https_port:="4443"}</span>
: <span class="hljs-variable">${caddy_http_port:="8880"}</span>

pidfile=<span class="hljs-string">"/var/run/caddy.pid"</span>
procname=<span class="hljs-string">"/usr/local/bin/caddy"</span>
<span class="hljs-built_in">command</span>=<span class="hljs-string">"/usr/sbin/daemon"</span>
command_args=<span class="hljs-string">"-f -p <span class="hljs-variable">${pidfile}</span> /usr/bin/env <span class="hljs-variable">${caddy_env}</span> <span class="hljs-variable">${procname}</span> -agree -http-port <span class="hljs-variable">${caddy_http_port}</span>  -https-port <span class="hljs-variable">${caddy_https_port}</span> -conf=<span class="hljs-variable">${caddy_conf}</span> -log=<span class="hljs-variable">${caddy_log}</span> <span class="hljs-variable">${caddy_args}</span>"</span>
extra_commands=<span class="hljs-string">"reload"</span>

start_precmd=caddy_startprecmd
reload_cmd=caddy_reloadcmd

<span class="hljs-function"><span class="hljs-title">caddy_startprecmd</span></span>()
{
      <span class="hljs-keyword">if</span> [ ! -e <span class="hljs-variable">${pidfile}</span> ]; <span class="hljs-keyword">then</span>
              install -o <span class="hljs-variable">${caddy_user}</span> -g <span class="hljs-variable">${caddy_group}</span> /dev/null <span class="hljs-variable">${pidfile}</span>;
      <span class="hljs-keyword">fi</span>
}

<span class="hljs-function"><span class="hljs-title">caddy_reloadcmd</span></span>()
{
      <span class="hljs-built_in">kill</span> -s USR1 $(cat <span class="hljs-variable">${pidfile}</span>)
}

run_rc_command <span class="hljs-string">"<span class="hljs-variable">$1</span>"</span>
</code></pre>
<p>Remove the <code>caddy</code> executable if you haven't already. Then create a new file with <code>vi</code>. This will be your <code>rc.d</code> script!</p>
<pre><code>vi caddy
</code></pre><p>Then paste the contents of the above script in there, save and exit.</p>
<p>Make sure the file is executable by using <code>chmod</code> and copy to the Caddy container.</p>
<pre><code>chmod +x caddy
bastille cp caddy caddy /usr/local/etc/rc.d/
</code></pre><p>Finally, we'll need a Caddyfile. Here's an example of one:</p>
<pre><code>stage.jaredwolff.com {
  tls hello@jaredwolff.com
  log /home/caddy/stage.jaredwolff.com.log
  root /<span class="hljs-keyword">var</span>/www/stage.jaredwolff.com/
  gzip
  log stderr
}
</code></pre><p><code>log</code> refers to this site specific access log.</p>
<p><code>root</code> refers to where the root <code>public</code> folder is on your machine. In my case it's the common <code>/var/www/&lt;name of site&gt;</code>. Set your paths and remember them. We'll need them later!</p>
<p>To have Caddy generate certs for this subdomain, you'll have to set the <em>tls</em> option. An email is all that's needed.</p>
<p>For more on the Caddyfile structure <a target="_blank" href="https://caddyserver.com/docs/caddyfile">check out the documentation.</a></p>
<p>Make a file called <code>caddyfile.conf</code> and copy it to <code>/usr/local/etc/</code> in your Caddy container:</p>
<pre><code>vi caddyfile.conf
# Paste your caddyfile contents and save
bastille cp caddy caddyfile.conf /usr/local/etc/
</code></pre><p>You should now redirect your DNS to the server IP. That way Caddy can generate/fetch the correct certificates. Then you can start Caddy with:</p>
<pre><code>bastille service caddy caddy start
</code></pre><p>You can check the log at <code>/usr/home/caddy/caddy.log</code> to make sure that your domain provisioned correctly.</p>
<p><strong><em>Side note:</em></strong> Getting setup with SSL certs is tough at first, especially if you're migrating from another server. Your site will have to go down for a little bit while you switch your DNS settings and start <code>caddy</code>. </p>
<p>(That's if you're using standard <code>caddy</code> 1.0. You can also use the DNS provider <a target="_blank" href="https://github.com/caddyserver/dnsproviders">plugins here</a> which make things a little easier.)</p>
<p>Now that we have <code>caddy</code> up and running it's time to copy our <code>hugo</code> generated assets over using <code>rsync</code>. We're off to the next step!</p>
<h2 id="heading-make-building-and-deploying-easy"><em>Make</em> building and deploying easy</h2>
<p><img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/make.png" alt="images/make.png" width="730" height="486" loading="lazy"></p>
<p>I spend a ton of time writing C code, and that means I spend tons of time using Makefiles. For many, <code>make</code> (or <code>gmake</code> for GNU make) is the bane of their existence. </p>
<p>For building and deploying, <code>make</code> makes it easy to create reusable recipes. That way you know you can deploy with confidence every time.</p>
<p>My Makefile borrows from the one that <a target="_blank" href="https://victoria.dev/blog/a-portable-makefile-for-continuous-delivery-with-hugo-and-github-pages/">Victoria Drake had posted</a> not too long ago. I changed it up a bit to match my needs. </p>
<p>Let's take a tour and see what's inside:</p>
<pre><code class="lang-Makefile"><span class="hljs-section">.POSIX:</span>

HUGO_VERSION := 0.66.0

OPTIMIZED_DIR := optimized
CONTENT_DIR := content
DEST_DIR := public

SERVER := 123.456.789.10
USER := user
</code></pre>
<p>The first section contains all the variables that I use to tell the functions later on what to do. It also has a reference to the <code>.POSIX</code> target. This means that the Makefile will be as portable between different versions of <code>make</code>.</p>
<p>Then, I popped in some logic to determine whether I'm deploying to <em>stage</em> or <em>production:</em></p>
<pre><code class="lang-Makefile"><span class="hljs-comment"># Set the place where it's deployed to.</span>
<span class="hljs-keyword">ifdef</span> PRODUCTION
<span class="hljs-variable">$(info Building for production. ?)</span>
TARGET := www
<span class="hljs-keyword">else</span>
<span class="hljs-variable">$(info Building for development. ?)</span>
BASEURL := --baseURL <span class="hljs-string">"https://stage.jaredwolff.com"</span>
TARGET := stage
<span class="hljs-keyword">endif</span>
</code></pre>
<p>By default, recipes below will use the development workflow. To use the production workflow, you can invoke <code>make</code> like this:</p>
<pre><code class="lang-Makefile">PRODUCTION=1 make build
</code></pre>
<p>This does add some extra friction to the deploy process. It's a good step though. That way you're sure the deploy is going to the right place!</p>
<pre><code class="lang-Makefile"><span class="hljs-comment"># Full path</span>
DEPLOY_DIR := /usr/local/bastille/jails/caddy/root/path/to/<span class="hljs-variable">$(TARGET)</span>.jaredwolff.com
</code></pre>
<p>Using the <code>TARGET</code> variable above, I then define the path to my server assets. I'm using Bastille to organize my jails, so the path is extra long. (yea, lengthly long) This allows us to use <code>rsync</code> to deploy the files with ease.</p>
<p>Now here come the fun bits. To do a full bulk resize, I'm using the <code>wildcard</code> functionality of the Makefile.</p>
<pre><code class="lang-Makefile">IMAGES := \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.jpg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.JPG)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.jpeg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.png)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.jpg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.jpeg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.png)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.JPG)</span> \
</code></pre>
<p>In this case it will create a huge space delimited list of every image that is within my content directory. The biggest drawback of this method is that it's not space tolerant. An easy fix to this is to make sure that all my photos do not have spaces.</p>
<p>Here's a quick and dirty bash command. You can use to rename files that have spaces and replace them with '_' characters:</p>
<pre><code class="lang-Makefile">for f in *\ *; do mv <span class="hljs-string">"$f"</span> <span class="hljs-string">"${f// /_}"</span>; done
</code></pre>
<p>Next, we rename these entries so the prefix is now the target directory. This will be useful when we want to resize:</p>
<pre><code class="lang-Makefile">OPTIMIZED_IMAGES := \
<span class="hljs-variable">$(<span class="hljs-built_in">subst</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/,<span class="hljs-variable">$(OPTIMIZED_DIR)</span>/,<span class="hljs-variable">$(IMAGES)</span>)</span>
</code></pre>
<p>Now check out the <code>optimize</code> recipe:</p>
<pre><code class="lang-Makefile"><span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: optimize</span>
<span class="hljs-section">optimize: build <span class="hljs-variable">$(OPTIMIZED_IMAGES)</span></span>
@echo <span class="hljs-string">"? Optimizing images"</span>
rsync -r <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/ <span class="hljs-variable">$(DEST_DIR)</span>/
du -sh <span class="hljs-variable">$(CONTENT_DIR)</span>/
du -sh <span class="hljs-variable">$(DEST_DIR)</span>/

<span class="hljs-variable">$(OPTIMIZED_IMAGES)</span>:
convert -strip -compress JPEG -resize '730&gt;' <span class="hljs-variable">$(<span class="hljs-built_in">subst</span> <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/,<span class="hljs-variable">$(CONTENT_DIR)</span>/,<span class="hljs-variable">$@</span>)</span> <span class="hljs-variable">$@</span>
</code></pre>
<p>It first calls the <code>build</code> recipe and then also the <code>$(OPTIMIZED_IMAGES)</code> recipe. The later will optimize the image using the <code>convert</code> command from <a target="_blank" href="https://imagemagick.org/script/convert.php">Imagemagick</a>. In this case I'm only resizing files that are larger than 730px wide. Change yours accordingly so you can reap the benefits of an <a target="_blank" href="https://www.jaredwolff.com/seven-ways-to-optimize-your-site-for-speed/">optimized site.</a></p>
<p>After resizing, the recipe uses <code>rsync</code> to copy the files from the <code>OPTIMIZED_DIR</code> to <code>DEST_DIR.</code></p>
<p>If we take a look at the <code>build</code> recipe, I first building the assets. Then, I copy the photos from the <code>content</code> dir to <code>optimized</code> dir. The nice thing is that <code>rsync</code> will only move files that have changed. Thus it doesn't have to copy the files over and over and over again every time you build.</p>
<p>Finally, the <code>deploy</code> recipe.</p>
<pre><code class="lang-Makefile"><span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: deploy</span>
<span class="hljs-section">deploy:</span>
@echo rsync to <span class="hljs-variable">$(DEPLOY_DIR)</span>
@rsync -r --del public/ <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>:<span class="hljs-variable">$(DEPLOY_DIR)</span>/
@echo making restic snapshot
@scp scripts/backup.sh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>:/root/backup.sh
@ssh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span> sh /root/backup.sh <span class="hljs-variable">$(DEPLOY_DIR)</span>
@echo <span class="hljs-string">"? Site is deployed!"</span>
</code></pre>
<p>You can see again that I'm using rsync to sync the contents of <code>public/</code> to the server. Make sure you set the <code>USER</code> , <code>SERVER</code> and <code>DEPLOY_DIR</code>. In my case <code>DEPLOY_DIR</code> comes out to <code>/usr/local/bastille/jails/caddy/root/var/www/www.jaredwolff.com</code></p>
<p>When you do finally get a successful deploy you can double check everything is in the correct place. Then once everything looks good you can start up your caddy server using:</p>
<pre><code>bastille service caddy caddy start
</code></pre><p><code>deploy</code> will also do something extra handy here. It will deploy my <code>restic</code> backup script and run it. I'll talk about this more in the backup section.</p>
<p>All in all, here's the full Makefile:</p>
<pre><code class="lang-Makefile"><span class="hljs-section">.POSIX:</span>

HUGO_VERSION := 0.66.0

OPTIMIZED_DIR := optimized
CONTENT_DIR := content
DEST_DIR := public

SERVER := 155.138.230.8
USER := root

<span class="hljs-comment"># Set the place where it's deployed to.</span>
<span class="hljs-keyword">ifdef</span> PRODUCTION
<span class="hljs-variable">$(info Building for production. ?)</span>
TARGET := www
<span class="hljs-keyword">else</span>
<span class="hljs-variable">$(info Building for development. ?)</span>
BASEURL := --baseURL <span class="hljs-string">"https://stage.jaredwolff.com"</span>
TARGET := stage
<span class="hljs-keyword">endif</span>

<span class="hljs-comment"># Full path</span>
DEPLOY_DIR := /usr/local/bastille/jails/caddy/root/var/www/<span class="hljs-variable">$(TARGET)</span>.jaredwolff.com

IMAGES := \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.jpg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.JPG)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.jpeg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.png)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.jpg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.jpeg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.png)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.JPG)</span> \

OPTIMIZED_IMAGES := \
<span class="hljs-variable">$(<span class="hljs-built_in">subst</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/,<span class="hljs-variable">$(OPTIMIZED_DIR)</span>/,<span class="hljs-variable">$(IMAGES)</span>)</span>

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: all</span>
<span class="hljs-section">all: build optimize</span>

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: clean</span>
<span class="hljs-section">clean:</span>
rm -rf public/
rm -rf optimized/

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: serve</span>
<span class="hljs-section">serve:</span>
@hugo serve -D

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: ssh</span>
<span class="hljs-section">ssh:</span>
@ssh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: build</span>
<span class="hljs-section">build:</span>
@echo <span class="hljs-string">"? Generating site"</span>
hugo --gc --minify -d <span class="hljs-variable">$(DEST_DIR)</span> <span class="hljs-variable">$(BASEURL)</span>
rsync -av --del -f<span class="hljs-string">"+ */"</span> -f<span class="hljs-string">"- *"</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/ <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: optimize</span>
<span class="hljs-section">optimize: build <span class="hljs-variable">$(OPTIMIZED_IMAGES)</span></span>
@echo <span class="hljs-string">"? Optimizing images"</span>
rsync -r <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/ <span class="hljs-variable">$(DEST_DIR)</span>/
du -sh <span class="hljs-variable">$(CONTENT_DIR)</span>/
du -sh <span class="hljs-variable">$(DEST_DIR)</span>/

<span class="hljs-variable">$(OPTIMIZED_IMAGES)</span>:
convert -strip -compress JPEG -resize '730&gt;' <span class="hljs-variable">$(<span class="hljs-built_in">subst</span> <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/,<span class="hljs-variable">$(CONTENT_DIR)</span>/,<span class="hljs-variable">$@</span>)</span> <span class="hljs-variable">$@</span>

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: deploy</span>
<span class="hljs-section">deploy:</span>
@echo rsync to <span class="hljs-variable">$(DEPLOY_DIR)</span>
@rsync -r --del public/ <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>:<span class="hljs-variable">$(DEPLOY_DIR)</span>/
@echo making restic snapshot
@scp scripts/backup.sh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>:/root/backup.sh
@ssh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span> sh /root/backup.sh <span class="hljs-variable">$(DEPLOY_DIR)</span>
@echo <span class="hljs-string">"? Site is deployed!"</span>
</code></pre>
<p>There are a few other handy nuggets in there you may want to use.  <code>clean</code>, <code>serve</code> and <code>ssh</code> have been very helpful when testing and connecting.</p>
<p>In the end you'll have a two step deploy process. The first generates your site with optimized images. The second is deploying to a server for static hosting.</p>
<h2 id="heading-incremental-backup">Incremental Backup</h2>
<p><img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Backup.png" alt="images/Backup.png" width="730" height="486" loading="lazy"></p>
<p>After discovering <a target="_blank" href="https://restic.net">Restic</a> I've been sold on how handy it has been for all my incremental backup needs. In the case of my server, I'm using to back up the root folder of my site. That way, if I need to roll back, I can do so with a few short steps.</p>
<p>Here's how you can set up a local <code>restic</code> repo.</p>
<h3 id="heading-setting-it-up">Setting it up</h3>
<p>Initializing the repo is simple. The most important part is making sure you <strong>don't lose/forget your password!</strong></p>
<pre><code>    # restic init -r /root/backups
    enter password <span class="hljs-keyword">for</span> <span class="hljs-keyword">new</span> repository:
    enter password again:
    created restic repository <span class="hljs-number">32e14</span>c7052 at /root/backups

    Please note that knowledge <span class="hljs-keyword">of</span> your password is required to access
    the repository. Losing your password means that your data is
    irrecoverably lost.
</code></pre><p>Set the <code>RESTIC_PASSWORD</code> environment variable to avoid entering your password. To make it permanent you'll have to place <code>export RESTIC_PASSWORD="Your password here!"</code> within the <code>.profile</code> file in <code>/root/</code>.</p>
<h3 id="heading-backing-up">Backing Up</h3>
<p>Invoking <code>restic</code> over SSH is tough. So our next best bet?</p>
<p>Transfer a (very brief) shell script to the server and run it after a deploy. Here's the contents of what I'm using today:</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/sh</span>
<span class="hljs-built_in">export</span> RESTIC_PASSWORD=<span class="hljs-string">"Your password here!"</span>
restic backup <span class="hljs-variable">$1</span> -r /root/backups/
</code></pre>
<p><strong><em>Side note:</em></strong> As I sit here and look at this script, for security reasons you can replace "Your password here!" with $2 which is the second argument to the script. That way you don't need to commit/push the password stored in a static file!</p>
<p>This first sets your backup password. Then it runs <code>restic</code> using the first command line argument as the path. So, to run a backup with this script, it would look something like this:</p>
<pre><code>./backup.sh /path/to/your/public/folder/
</code></pre><p><strong>Note:</strong> you do need to initialize your <code>restic</code> backup <em>before</em> you start backing up. It will barf at you otherwise!</p>
<p>In my case I'm placing the incremental backups on a different folder of my machine. That way they're easily accessible and <em>fast</em>.</p>
<h3 id="heading-viewing-your-backups">Viewing your backups</h3>
<p>To view your backups you can run the following command:</p>
<h1 id="heading-restic-snapshots-r-rootbackups-g-paths-c">restic snapshots -r /root/backups -g paths -c</h1>
<p>    enter password for repository:
    repository e140b5e4 opened successfully, password is correct
    snapshots for (paths [/usr/local/bastille/jails/caddy/root/var/www/www.jaredwolff.com]):</p>
<h2 id="heading-id-time-host-tags">    ID        Time                 Host         Tags</h2>
<p>    d3328066  2020-03-10 00:30:58  vultr.guest
    f3360819  2020-03-10 04:03:03  vultr.guest
    231dd134  2020-03-10 04:44:00  vultr.guest
    3c1be26a  2020-03-10 04:56:19  vultr.guest
    e96c947c  2020-03-10 05:03:00  vultr.guest
    34c3682a  2020-03-10 14:01:37  vultr.guest
    fbccdb8c  2020-03-10 14:04:26  vultr.guest
    9ce11146  2020-03-10 15:38:49  vultr.guest
    046b3da3  2020-03-10 15:47:06  vultr.guest
    9c28d4bc  2020-03-10 15:48:25  vultr.guest
    469dc228  2020-03-10 15:48:54  vultr.guest
    6f78af72  2020-03-10 17:00:21  vultr.guest
    29ad17b2  2020-03-10 20:18:23  vultr.guest
    ed22ce1f  2020-03-10 20:20:24  vultr.guest
    9c8c1b03  2020-03-11 13:56:40  vultr.guest
    b6cfcfec  2020-03-11 14:08:14  vultr.guest
    e8546005  2020-03-11 14:27:22  vultr.guest
    49a134fe  2020-03-17 00:47:58  vultr.guest</p>
<h2 id="heading-c0beb283-2020-03-18-204452-vultrguest">    c0beb283  2020-03-18 20:44:52  vultr.guest</h2>
<p>You can use this list to determine if you need to roll back a deploy.</p>
<h3 id="heading-restoring">Restoring</h3>
<p>Restoring from a backup, especially in a live environment, needs to be quick. After viewing your backups you can restore a specific backup by using its <em>ID</em>.</p>
<pre><code>restic restore d3328066
</code></pre><p>This will restore the files back to the backup made on <em>2020-03-10 00:30:58.</em> Awesome. Plus it won't overwrite every single file. It will only apply the differences from the current state and the stored state.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We've covered a ton of ground in this post. You've learned how to:</p>
<ul>
<li>Deploy your own server using Vultr</li>
<li>Use Bastille to create Container-like Jails</li>
<li>Set up Caddy to serve static file assets with TLS</li>
<li>Deploy the files using a fairly simple Makefile and <code>rsync</code></li>
<li>Back up after every deploy using <code>restic</code></li>
</ul>
<p>In the end we have a robust, secure and simple platform for hosting static files and services. </p>
<p>Stay tuned as there are more posts like this coming your way soon! In the meantime check out my <a target="_blank" href="https://www.jaredwolff.com/blog/">other posts.</a> </p>
<p>Thanks for reading and see you next time! ?</p>
<p><strong>You can find other articles like this at <a target="_blank" href="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/">www.jaredwolff.com.</a></strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Publish a no-code website in 10 minutes ]]>
                </title>
                <description>
                    <![CDATA[ In this article, I'll introduce a no-code, no-software and no-cost solution to publishing sophisticated web sites managed by non-technical people. The full codebase is on GitHub here. Sir Issac Newton discovered the law of gravity when practicing “so... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/publish-a-no-code-website-in-10-minutes/</link>
                <guid isPermaLink="false">66d4604ec7632f8bfbf1e459</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub Actions ]]>
                    </category>
                
                    <category>
                        <![CDATA[ github pages ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Hugo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ No Code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Static Site Generators ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 28 Mar 2020 20:59:47 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/igor-miske-Px3iBXV-4TU-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, I'll introduce a no-code, no-software and no-cost solution to publishing sophisticated web sites managed by non-technical people. The full codebase is <a target="_blank" href="https://github.com/second-state/hugo-website">on GitHub here</a>.</p>
<p>Sir Issac Newton discovered the law of gravity when practicing “social distancing” during the Plague. What will YOU do? One silver lining of quarantine is that all this free time brings out the entrepreneur spirit and creativity in us.</p>
<p>However, especially because of the quarantine, now more than ever, any new idea or project must have a web site. Traditional CMS solutions like Wordpress, Squarespace, or Wix are difficult to use, look dated, are expensive, or all of the above!</p>
<p>We wanted to create a web site that has a sophisticated look and feel, yet is easy to customize. A non-technical person should be able to edit the source files and see the changes appear on the live web site in a few minutes. Ideally, it should also be free (forever free, not just free-for-now), and can scale to handle millions of visitors if it becomes popular.</p>
<p>Is this possible?</p>
<p>In this short article, I will walk you through a solution based on the Hugo framework, GitHub Pages, and GitHub Actions. You can get up and running with your shiny new website by the end of this article.</p>
<blockquote>
<p>It is so easy that my 9-year-old son did it. He now manages a web site for his fictional country called <a target="_blank" href="https://www.arenztopia.com/">Arenztopia</a>. Check out the <a target="_blank" href="https://medium.com/@michaelyuan_88928/welcome-to-arenztopia-95cc85253163">back story</a>.</p>
</blockquote>
<h2 id="heading-tldr">TL;DR</h2>
<p>If you just want to get started with a working web site as soon as possible, first make sure that you have a free GitHub account.</p>
<p>Go to <a target="_blank" href="https://github.com/second-state/hugo-website">this GitHub repository</a>, and click on the “Fork” button on the top right, and <strong>fork</strong> it to your own account.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/1_edHfJdOu6Ppjiyd-M4tMJQ.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Go to your forked repository, and click on the Actions tab. You will see a message like the one in the picture below. <strong>Click on</strong> the “I understand my workflows …” button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/1_nCrYAyGhCmLeFC2PMbUBEA.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Go to the Settings tab of your repository, and then scroll down to GitHub Pages. <strong>Re-select</strong> the <code>gh-pages</code> from the dropdown for the web site to build.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/1_K4qXL2CN2rtMonI4dXRWnA.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Go to the Code tab of the repository, open the <code>config.toml</code> file, and edit it. <strong>Change</strong> its <code>title</code> attribute to something else, and click on the “Commit changes” button at the bottom. We need this step to trigger the workflow at the new repository.</p>
<p>Wait a few minutes, go to the “published at” web address at GitHub Pages and you will see the template web site.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/1_WRQaxyvCqagRMfsN6T1B_A.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Next, you can customize the site by editing the <code>config.toml</code> file and the files in the <code>content</code> folder. Go to the “Add your own content” section at the end of this article to see how. You can check out <a target="_blank" href="https://github.com/budparr/gohugo-theme-ananke#getting-started">the instructions for the Ananke theme here</a>.</p>
<p>That’s it for the quick start! In the next several sections, I will explain in more detail the concepts and processes.</p>
<h2 id="heading-hugo-basics">Hugo basics</h2>
<p>Older generation CMS solutions like Wordpress are too difficult to adapt to new web site designs, and commercially hosted solutions like SquareSpace are too expensive and not flexible enough. Static web site generators like Hugo offer a good balance among features, flexibility, and ease of authoring.</p>
<ul>
<li><p>Hugo web sites can be customized and modified through configuration files.</p>
</li>
<li><p>New pages and content sections can be written in markdown instead of HTML. That makes content authoring much easier.</p>
</li>
<li><p>There are many modern design themes to choose from.</p>
</li>
<li><p>The output of Hugo is a static HTML web site that can be deployed on any low-cost hosting provider.</p>
</li>
<li><p>Together with static web site hosting services like GitHub Pages and Netlify, they can offer a completely free solution.</p>
</li>
</ul>
<p>The Hugo software distribution is <a target="_blank" href="https://gohugo.io/getting-started/installing/">available</a> on all major operating systems. You can <a target="_blank" href="https://gohugo.io/getting-started/quick-start/">learn about it here</a>. But, since we will use GitHub Actions to automatically build our Hugo web sites, we do not actually need to install Hugo software here.</p>
<p>Here is how to do it.</p>
<h2 id="heading-create-a-template-website">Create a template website</h2>
<p>First, select a Hugo theme. There are <a target="_blank" href="https://themes.gohugo.io/">many</a>. Some are geared toward web sites with one or more content web pages, while others are optimized for blog-like sites with timestamped posts.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/1_CjWlyVScCKNkxDIxshwYTQ.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Hugo themes</em></p>
<p>Find one you like, download a zip package or clone a GitHub repo, and unpack the theme into a folder. Let’s assume that the theme distribution is unpacked into a folder called <code>my-theme</code>. The following are commands in a Linux terminal. You could use the Terminal app on Mac or PowerShell on Windows.</p>
<p>Next, create your web site project directory on your computer.</p>
<pre><code class="lang-javascript">$ mkdir -r my-site/themes
</code></pre>
<p>Copy the theme folder into your project.</p>
<pre><code class="lang-javascript">$ cp -r my-theme my-site/themes
</code></pre>
<p>Next, copy the theme’s <code>exampleSite</code> to the project’s top-level directory.</p>
<pre><code class="lang-javascript">$ cd my-site
$ cp -r themes/my-theme/exampleSite<span class="hljs-comment">/* ./</span>
</code></pre>
<p>Edit the <code>config.toml</code> in the project root directory <code>my-site/</code> to point to the right theme.</p>
<pre><code class="lang-javascript">baseURL = <span class="hljs-string">"/"</span>themesDir = <span class="hljs-string">"themes"</span>theme = <span class="hljs-string">"my-theme"</span>
</code></pre>
<p>Next, create a GitHub repo called <code>my-site</code>, and push the <code>my-site</code> directory onto its <code>master</code> branch. Here are the steps for <a target="_blank" href="https://help.github.com/en/github/managing-files-in-a-repository/adding-a-file-to-a-repository">uploading files from GitHub’s web UI</a>. Now we are ready to publish the theme example site.</p>
<p>For a Hugo-based system to be useable to a non-developer (or a young developer who has yet to master the command line tools), we must automate the process of building and deploying the static web site.</p>
<h2 id="heading-automate-deployment">Automate deployment</h2>
<p>In the GitHub project, go to Settings and enable GitHub Pages. Select the source to be the <code>gh-pages</code> branch.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/1_TrbrZti-GFpVBK0HtrezcA.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Settings, GitHub Pages</em></p>
<p>Next, we create a GitHub Actions workflow to run the Hugo command on the source files from <code>master</code> branch, and push the generated HTML files to the <code>gh-pages</code> branch for publication. From the project’s Actions tab, click on the “set up a workflow yourself” button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/1_b5UuewhfFq2Gfa0vwJ8bqw.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Set up a workflow yourself</em></p>
<p>The workflow is stored in the <code>master</code> branch as <code>.github/workflows/main.yml</code> file. The content of the file is as follows.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">github</span> <span class="hljs-string">pages</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-18.04</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>  <span class="hljs-comment"># v2 does not have submodules option now</span>
        <span class="hljs-comment"># with:</span>
        <span class="hljs-comment">#   submodules: true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Hugo</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">peaceiris/actions-hugo@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">hugo-version:</span> <span class="hljs-string">'0.62.2'</span>
          <span class="hljs-attr">extended:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">hugo</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">peaceiris/actions-gh-pages@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">github_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">publish_dir:</span> <span class="hljs-string">./public</span>
</code></pre>
<p>What happens here is that the web site authors and editors will change content and files on the <code>master</code> branch. Whenever new content is pushed to the <code>master</code> branch, the automated GitHub Actions workflow will <a target="_blank" href="https://github.com/peaceiris/actions-hugo/blob/master/README.md">set up the Hugo software</a>, run the <code>hugo</code> command, and turn those files into HTML files for a static web site.</p>
<p>The HTML files are <a target="_blank" href="https://github.com/peaceiris/actions-gh-pages/blob/master/README.md">pushed</a> to the <code>gh-pages</code> branch of the same repository. They will be published on the specified web address by GitHub Pages as configured.</p>
<p>Notice the <code>cname</code> attribute in the last line. That is the <a target="_blank" href="https://help.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site">custom domain name</a> we set up with GitHub Pages. If you do not have a custom domain name, just remove this line, and you can access your web site at the domain provided by GitHub Pages.</p>
<p>Now go to the web site, and you should see the theme’s default web page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/1_fkJkt7YXYEZX96Qv3v5u8A.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>The HugoSerif template for one of our web sites.</em></p>
<h2 id="heading-add-your-own-content">Add your own content</h2>
<p>To change the default theme web site to your own content, you just need to change the files on the <code>master</code> branch. Please refer to the <a target="_blank" href="https://gohugo.io/content-management/organization/">documentation</a> of your selected theme. In general, Hugo templates work like this:</p>
<ul>
<li><p>The web pages are authored in markdown format and the <code>md</code> files are in the <code>content</code> folder.</p>
</li>
<li><p>Each <code>md</code> file has a header section with properties such as the page’s menu placement, priority, timestamp, excerpt, etc.</p>
</li>
<li><p>The overall configuration, such as the menu items and properties used by multiple pages, are stored in the <code>data</code> folder.</p>
</li>
<li><p>Static content such as raw HTML files, JavaScript files, and image files can be placed in the <code>static</code> folder.</p>
</li>
</ul>
<p>In particular, here is how you customize the Ananke theme that comes with our template:</p>
<ul>
<li><p>The <a target="_blank" href="https://github.com/second-state/hugo-website/blob/master/config.toml">config.toml</a> file allows you to configure the website title, social icons on all pages, and the big featured image on the home page.</p>
</li>
<li><p>All images should be uploaded to the <a target="_blank" href="https://github.com/second-state/hugo-website/tree/master/static/images">static/images</a> folder.</p>
</li>
<li><p>The <a target="_blank" href="https://github.com/second-state/hugo-website/blob/master/content/_index.md">content/_index.md</a> file contains the text for the home page.</p>
</li>
<li><p>To add pages to the site, you can just create <a target="_blank" href="https://guides.github.com/features/mastering-markdown/">markdown</a> files in the <a target="_blank" href="https://github.com/second-state/hugo-website/tree/master/content">content</a> folder. An example is the <a target="_blank" href="https://github.com/second-state/hugo-website/blob/master/content/contact.md">contact.md</a> file. Notice that at the top of the file, there are attributes to control whether this page should be on the website menu.</p>
</li>
<li><p>To add articles to the site, you can create markdown files in the <a target="_blank" href="https://github.com/second-state/hugo-website/tree/master/content/post">content/post</a> folder. Those are blog-like content articles that have dates and titles at the top. The most recent two articles will show up on the home page.</p>
</li>
</ul>
<p>If you are interested in learning more and see how we did it, you can watch our progress at</p>
<ul>
<li><p>The Country of Arenztopia [<a target="_blank" href="https://github.com/juntao/arenztopia">GitHub</a>] [<a target="_blank" href="https://www.arenztopia.com/">Web site</a>]</p>
</li>
<li><p>Second State blog [<a target="_blank" href="https://github.com/second-state/blog">GitHub</a>] [<a target="_blank" href="https://blog.secondstate.io/categories/en/">Web site</a>]</p>
</li>
</ul>
<p>Good luck and stay healthy!</p>
<h2 id="heading-about-the-author">About the author</h2>
<p>Dr. Michael Yuan is the <a target="_blank" href="http://www.michaelyuan.com/">author of 5 books</a> on software engineering. His latest book <a target="_blank" href="https://www.buildingblockchainapps.com/">Building Blockchain Apps</a> was published by Addison-Wesley in Dec 2019. Dr. Yuan is the co-founder of <a target="_blank" href="https://www.secondstate.io/">Second State</a>, a VC-funded startup that brings WebAssembly and Rust technologies to <a target="_blank" href="https://www.secondstate.io/articles/why-webassembly-server/">cloud</a>, <a target="_blank" href="https://docs.secondstate.io/">blockchain</a>, and <a target="_blank" href="https://github.com/second-state/rust-wasm-ai-demo/blob/master/README.md">AI</a> applications. It enables developers to deploy fast, safe, portable, and serverless <a target="_blank" href="https://www.secondstate.io/articles/getting-started-with-rust-function/">Rust functions on Node.js</a>.</p>
<div class="embed-wrapper"><iframe src="https://webassemblytoday.substack.com/embed" width="480" height="320" style="border:1px solid #EEE;background:white" title="Embedded content" loading="lazy"></iframe></div>

<p>Prior to Second State, Dr. Yuan was a long time open source contributor at Red Hat, JBoss, and Mozilla. Outside of software, Dr. Yuan is a Principal Investigator at the National Institutes of Health, with multiple research awards on cancer and public health research. He holds a PhD in astrophysics from the University of Texas at Austin.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create Your First Hugo Blog: a Practical Guide ]]>
                </title>
                <description>
                    <![CDATA[ Hugo is a great tool to use if you want to start a blog. I use Hugo myself for my blog, flaviocopes.com, and I've been using it for more than two years. I have a few reasons for loving Hugo. First of all, it is simple, boring, flexible, and fast. ]]>
                </description>
                <link>https://www.freecodecamp.org/news/your-first-hugo-blog-a-practical-guide/</link>
                <guid isPermaLink="false">66bb5ad3965d5c9ed5487bb7</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Hugo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technical writing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Flavio Copes ]]>
                </dc:creator>
                <pubDate>Wed, 08 Jan 2020 08:44:58 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/01/Screen-Shot-2020-01-03-at-20.05.40.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hugo is a great tool to use if you want to start a blog.</p>
<p>I use Hugo myself for my blog, <a target="_blank" href="https://flaviocopes.com/">flaviocopes.com</a>, and I've been using it for more than two years. I have a few reasons for loving Hugo.</p>
<p>First of all, it is <strong>simple</strong>, <strong>boring</strong>, <strong>flexible</strong>, and <strong>fast</strong>.</p>
<p>The main reason is that it is <strong>simple</strong>. There’s not much you have to learn to get started.</p>
<p>You write content in Markdown, a format that lets me use my favorite editor (Bear) to write posts.</p>
<p>Hugo is <strong>boring</strong>. Don’t get me wrong, this is a very positive thing. As a developer I am tempted to tweak things here and there all the time. There’s no fancy technology underlying Hugo. It’s built using Go, one of the languages I love the most, but that does not mean I want to dive into the internals of Hugo and change how it works.</p>
<p>And it does not surface any cool or next-generation stuff like many JavaScript frameworks tend to do.</p>
<p>Hence it is boring, which gives me a lot of time to do what is really useful when working on a blog: <strong>writing content</strong>. I focus on the content, not on the content container.</p>
<p>That said, Hugo is pretty darn <strong>flexible</strong>. I started my own blog with an open source theme, then changed it completely over time. Sometimes I want to do things in my website that are out of the scope of a simple blog, and Hugo allows me to create those things.</p>
<p>Finally, another reason I love Hugo is that it is <strong>fast</strong>. Why? First, it has Go at the core, which is known to be a very fast language. And in the Go ecosystem, there’s no concept of 100 megabytes dependencies. Things are made to be as fast as possible. Plus, Hugo does not need to do some of the fancy stuff that is needed when using fancy technology. This is a by-product of being boring.</p>
<p>Anyway, enough with words.</p>
<p>Hugo is amazing, especially if you are a developer and you’re willing to write in Markdown. Non-tech people might just refuse to use Markdown, and it’s perfectly understandable.</p>
<p>Also, you have to be prepared for a Git-centric workflow to make things really click.</p>
<p>This is the process for writing a blog: </p>
<ul>
<li>write a post using Markdown, </li>
<li>then commit your changes to a Git repository, most commonly on GitHub, </li>
<li>and then some glue technology deploys the changes on the server that hosts the site.</li>
</ul>
<h2 id="heading-hosting-a-hugo-website">Hosting a Hugo website</h2>
<p>A Hugo blog is completely <strong>static</strong>. This means you don’t need to host your own server, or use a special service for it.</p>
<p>Netlify, Now and GitHub Pages are three great places where you can host a Hugo blog, for free.</p>
<p>The only cost is the one you have to sustain for the domain name. I can’t stress enough the importance of having your own domain name. No <code>.github.io</code> or <code>.netlify.com</code> or <code>.now.sh</code> sites, please.</p>
<p>My own Hugo sites are hosted on Netlify.</p>
<h2 id="heading-choose-a-domain">Choose a domain</h2>
<p>Put your blog under your own domain. Pick one. Use your own name. And use <code>.com</code> or <code>.blog</code>. Don’t try to be clever by using a localized domain - for example, don’t use <code>.io</code>. <code>.com</code> just gives a better impression and it’s reusable for all your future projects, not just to host your blog. I picked that one.</p>
<p>Oh and if you have an old domain lying around, just use that. Why? The older your domain is, the better.</p>
<p>Note on subdomains: every subdomain, to Google, is a different website. So if your domain is <code>flaviocopes.com</code>, and you create your blog in <code>blog.flaviocopes.com</code>, then that’s a completely new website to Google, and it will have its own ranking separate from the main domain.</p>
<p>My suggestion is to avoid subdomains completely.</p>
<h2 id="heading-install-hugo">Install Hugo</h2>
<p>To install Hugo on macOS, from your terminal run</p>
<pre><code class="lang-bash">brew install hugo
</code></pre>
<p><em>The <code>brew</code> command does not exist on your Mac? Check the <a target="_blank" href="https://flaviocopes.com/homebrew/">Homebrew guide</a></em>.</p>
<p>For Windows and Linux, check the <a target="_blank" href="https://gohugo.io/getting-started/installing/">official installation guide</a>.</p>
<h2 id="heading-create-a-hugo-site">Create a Hugo site</h2>
<p>Once Hugo is installed, you can create a Hugo site by running</p>
<pre><code class="lang-bash">hugo new site myblog
</code></pre>
<p>I suggest that you run this into a <code>www</code> folder in your Home directory, because the command will create a new <code>myblog</code> folder where you run it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/hugo-cmd-tool.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-pick-a-theme">Pick a theme</h2>
<p>Now before you can start you need to pick a theme. I wish Hugo included a default theme to make things straightforward, but it does not.</p>
<p>There are a lot of choices on <a target="_blank" href="https://themes.gohugo.io/">https://themes.gohugo.io</a>. My personal recommendation is to start with <a target="_blank" href="https://themes.gohugo.io/ghostwriter/">https://themes.gohugo.io/ghostwriter/</a> and tweak it later.</p>
<p>I also recommend that you avoid the <code>git clone</code> workflow they suggest on that page. You’ll surely be tweaking the theme in the future, and I find it best to have a single repository for both content and theme. It simplifies deployment.</p>
<p>So, go to <a target="_blank" href="https://github.com/jbub/ghostwriter/archive/master.zip">https://github.com/jbub/ghostwriter/archive/master.zip</a> to download the current version of the theme.</p>
<p>Then unpackage it in the <code>themes/ghostwriter</code> folder in your newly created Hugo website:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/ghostwriter-theme.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Notice there is an <code>exampleSite</code> folder in the <code>themes/ghostwriter</code>. Open it, and open its <code>content</code> subfolder. In there, you can see the <code>page</code>, <code>post</code> and <code>project</code> subfolders.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/page-post-and-projects-subfolders.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Copy <code>page</code> and <code>post</code> in the <code>content</code> folder of the site:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/copy-page-and-post-directories.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-the-configuration">The configuration</h2>
<p>The sample data also provide a sample <code>config.toml</code> file in <code>themes/ghostwriter/exampleSite/config.toml</code>. This is the Hugo configuration file, which tells Hugo some details of the configuration without you having to hardcode information in the theme.</p>
<p>I recommend that you not copy that, because it has too many things, and instead use this:</p>
<pre><code class="lang-toml"><span class="hljs-attr">baseurl</span> = <span class="hljs-string">"/"</span>
<span class="hljs-attr">title</span> = <span class="hljs-string">"My blog"</span>
<span class="hljs-attr">theme</span> = <span class="hljs-string">"ghostwriter"</span>

<span class="hljs-section">[Params]</span>
    <span class="hljs-attr">mainSections</span> = [<span class="hljs-string">"post"</span>]
    <span class="hljs-attr">intro</span> = <span class="hljs-literal">true</span>
    <span class="hljs-attr">headline</span> = <span class="hljs-string">"My headline"</span>
    <span class="hljs-attr">description</span> = <span class="hljs-string">"My description"</span>
    <span class="hljs-attr">github</span> = <span class="hljs-string">"https://github.com/XXX"</span>
    <span class="hljs-attr">twitter</span> = <span class="hljs-string">"https://twitter.com/XXX"</span>
    <span class="hljs-attr">email</span> = <span class="hljs-string">"XXX@example.com"</span>
    <span class="hljs-attr">opengraph</span> = <span class="hljs-literal">true</span>
    <span class="hljs-attr">shareTwitter</span> = <span class="hljs-literal">true</span>
    <span class="hljs-attr">dateFormat</span> = <span class="hljs-string">"Mon, Jan 2, 2006"</span>

<span class="hljs-section">[Permalinks]</span>
    <span class="hljs-attr">post</span> = <span class="hljs-string">"/:filename/"</span>
</code></pre>
<p>You can freely customize the information in this file later.</p>
<p>Now from the command line, run:</p>
<pre><code class="lang-bash">hugo serve
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/hugo-serve-output.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Open <code>http://localhost:1313</code> in your browser, and you should be able to see the site live!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/live-site-localhost.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This is the site home page.</p>
<p>There is a list of posts that is taken from the <code>content/post</code> folder of your website:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/content-post-samples.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click the first, called “Creating a New Theme”:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/creating-a-new-theme-post.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can open the file <code>content/post/creating-a-new-theme.md</code> to change anything in the post.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/updating-markdown-of-post.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you save, the website will automatically update with the new content.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/updated-post-live.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This is pretty awesome, right?</p>
<p>You can create a new post by creating a new <code>.md</code> file, prefixing it with anything you want. You can use incremental numbers, if you prefer. Or use a date.</p>
<p>If something doesn't look the way you want, you can open the <code>themes/ghostwriter/layouts</code> folder and tweak it.</p>
<p>The “post” template is defined in <code>themes/ghostwriter/layouts/post/single.html</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/post-template.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Hugo uses Go templates. The syntax can be pretty unfamiliar but the Hugo website does a very good job at explaining them in this <a target="_blank" href="https://gohugo.io/templates/introduction/">Go templates introduction</a>.</p>
<p>However, try to not look at customizing your template now.</p>
<p>If you want to tweak the colors, add a <code>&lt;style&gt;</code> tag with some CSS in the <code>themes/ghostwriter/layouts/partials/header.html</code>.</p>
<p>For example, make links black:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
<span class="hljs-selector-class">.site-title</span> <span class="hljs-selector-tag">a</span>, <span class="hljs-selector-class">.button-square</span> {
    <span class="hljs-attribute">background</span>: black;
}
<span class="hljs-selector-tag">a</span> {
    <span class="hljs-attribute">color</span>: black;
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>Focus on the content instead.</p>
<p>Remove the existing files, and write 2-3 posts to start with.</p>
<p>It’s too easy to get trapped in making things perfectly the way you want, but the important thing is the content.</p>
<p>And the cleaner your site is, the better for your readers.</p>
<p>Let me now write a little about deployment.</p>
<h2 id="heading-deploy-the-hugo-site-to-netlify">Deploy the Hugo site to Netlify</h2>
<p>I want to showcase how to deploy a Hugo site in 2 of the services I enjoy the most: Netlify and Now.</p>
<p>First, I’m going to create a GitHub repository to host the site.</p>
<p>I open GitHub Desktop, an app I use every day and that is part of my workflow. It’s the simplest way to use Git.</p>
<p>From the File menu, I pressed the “New Repository” option:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/netlify-new-repository.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The same screen can be generated by simply dragging the <code>myblog</code> folder into the app.</p>
<p>I gave the <code>myblog</code> name to the repository, and picked the correct path for the repo.</p>
<p>The process automatically makes the first commit:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/netlify-first-commit.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now we can click the “Publish repository” button to push the repo to GitHub:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/publish-repo.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can keep the repo private, of course.</p>
<p>Once the repo is in GitHub:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/private-repo-on-github.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>we can move to Netlify.</p>
<p>From my Netlify dashboard I pressed the “New site from Git” button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/new-site-from-git.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>I pressed GitHub, authorized Netlify to access my private repositories, then I picked the repo I just created:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/access-to-private-repos.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Netlify automatically identified it as a Hugo repo, and entered the build command automatically:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/netlify-enter-build-command.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Clicking “Deploy site” starts the deploy process:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/netlify-deploy-site.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>On a real site, I would set up a custom domain. Netlify has the option to purchase a domain through them, and it’s a very (VERY) straightforward process. I highly recommend it. The site can be live in just a few minutes after purchasing the domain.</p>
<p>A random <code>.netlify.com</code> subdomain is attached to the site, in this case <code>pedantic-engelbart-500c9a.netlify.com</code>, and HTTPS is automatically enabled.</p>
<p>We can therefore immediately see the site live:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/preview-netlify-subdomain.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now if you try to edit something in your local version, you just push the changes to GitHub, and Netlify will automatically update the site. You can see it building the site in the “Overview” panel of the site:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/automatic-build.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To learn more about Netlify I recommend that you check out my <a target="_blank" href="https://flaviocopes.com/netlify/">Netlify tutorial</a>.</p>
<h2 id="heading-deploy-the-hugo-site-to-zeit-now">Deploy the Hugo site to Zeit Now</h2>
<p>Another awesome platform you can use for your Hugo blog is Zeit Now.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/zeit-now.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once you sign up, from the dashboard you press the <strong>New Project</strong> button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/zeit-now-new-project.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The first time you deploy from GitHub you have to first install the GitHub app by clicking “Install Now For GitHub”:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/zeit-now-install-now-from-github.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This brings you to the GitHub page for the app, where you can authorize it for all your repos, or just for some:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/zeit-now-authorize.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once you get back, click the “New Project From GitHub” button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/zeit-now-new-project-from-github.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Select the project and click “Import”:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/zeit-now-import.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the meantime, go into the main folder of <code>mysite</code> and add a <code>package.json</code> file with this content:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"hugo"</span>
  }
}
</code></pre>
<p>This tells Now how to deploy the site.</p>
<p>When you get back to the dashboard, the new deploy should start soon, and you will see the site working live:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/zeit-now-deployed.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/zeit-now-live-site.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Note that in Now you have three URLs you can use to access the site:</p>
<ul>
<li><code>myblog.flaviocopes.now.sh</code></li>
<li><code>myblog-alpha-swart.now.sh</code></li>
<li><code>myblog-git-master.flaviocopes.now.sh</code></li>
</ul>
<p>You can choose the one you prefer.</p>
<p>Plus, each deployment has its own URL, too. In this case I had <code>myblog-h8xks5jhn.now.sh</code> but it changes with every deployment.</p>
<p>And of course you can add your domain, too. Zeit has a great service to purchase your domain directly from them, available at <a target="_blank" href="https://zeit.co/domains">https://zeit.co/domains</a>.</p>
<p>And if you prefer working with the command line, the <code>now</code> command lets you purchase domains from there, as well.</p>
<p>I highly recommend that you check out my <a target="_blank" href="https://flaviocopes.com/zeit-now/">Zeit Now tutorial</a> to learn more about this platform.</p>
<h2 id="heading-wrapping-up">Wrapping up</h2>
<p>I hope this tutorial can give you a little guidance if you are planning to start a new blog. Hugo is my favorite platform, but it's not unique of course. Ghost (the platform powering freeCodeCamp) is great too, along with WordPress of course, and Gatsby.</p>
<p>Pick your favorite. In my opinion the platform does not matter as much as your content does. So, choose one and start writing!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Gatsby vs Hugo, a detailed comparison ]]>
                </title>
                <description>
                    <![CDATA[ By David In this article, I compare two static site generators, Gatsby and Hugo. I discuss framework familiarity, stability, security, tooling, build speed, performance and the community surrounding both. So let’s get started. About a year ago, I cha... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/gatsby-vs-hugo-a-detailed-comparison-e78d94f640fc/</link>
                <guid isPermaLink="false">66d46013d1ffc3d3eb89de1a</guid>
                
                    <category>
                        <![CDATA[ GatsbyJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Hugo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Static Site Generators ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 28 Jul 2018 00:00:00 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*Qrk2M5aQ_J6Gtt-WZBrnGA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By David</p>
<p>In this article, I compare two static site generators, Gatsby and Hugo. I discuss framework familiarity, stability, security, tooling, build speed, performance and the community surrounding both. So let’s get started.</p>
<p>About a year ago, I changed <a target="_blank" href="https://learnitmyway.com/">my website</a> from Wordpress to <a target="_blank" href="http://gohugo.io/">Hugo</a>, which is a static site generator written in Go that uses Go’s template libraries for templating. I have recently done a viability assessment of <a target="_blank" href="https://www.gatsbyjs.org/">Gatsby</a>, another static site generator written in React that uses React for templating.</p>
<p>In this article, I compare the differences between Hugo v0.42 and Gatsby v1.93. For the comparison, I used <a target="_blank" href="https://learnitmyway-hugo.netlify.com/">this Hugo site</a> and <a target="_blank" href="http://learnitmyway-gatsby.netlify.com/">this Gatsby site</a>. The code for each can be found on Github for <a target="_blank" href="https://github.com/DeveloperDavo/learnitmyway/tree/gatsby-vs-hugo">the Hugo site</a> and for <a target="_blank" href="https://github.com/DeveloperDavo/learnitmyway-gatsby">the Gatsby site</a>.</p>
<h4 id="heading-framework-familiarity">Framework familiarity</h4>
<p>If you are not familiar with React and you don’t plan on learning React, then you should probably choose Hugo. If you know and like React, should you choose Gatsby? Well, not necessarily.</p>
<p>I would argue that you need a decent understanding of React (see <a target="_blank" href="https://learnitmyway.com/learn-react-with-these-resources/">Learn React with these Resources</a>) if you want to use Gatsby. And in order to understand React, you need a decent understanding of JavaScript (see <a target="_blank" href="https://learnitmyway.com/learn-javascript-with-these-resources/">Learn JavaScript with these resources</a>).</p>
<p>Even though I have been using Hugo for almost a year, it wasn’t necessary for me to understand Go. I also only had to learn a little bit about Go’s template libraries. However, I did find that I had to refer to the documentation more often with Hugo because of my lack of familiarity. Gatsby requires a deeper understanding of React than Hugo expects of Go. Nevertheless, if framework familiarity were the only criteria, I would choose Gatsby because it’s nice not to have to refer to the documentation while adding new features to my website.</p>
<h4 id="heading-stability">Stability</h4>
<p>One way of assessing stability would be to compare <a target="_blank" href="https://github.com/gohugoio/hugo/issues">Hugo’s issues on GitHub</a> with <a target="_blank" href="https://github.com/gatsbyjs/gatsby/issues">Gatsby’s issues on GitHub</a>. You will see that Gatsby has more features, (which is exciting) but also has more bugs (which is not so exciting). I initially did not consider stability as a criteria until I found <a target="_blank" href="https://github.com/gatsbyjs/gatsby/issues/6392">this bug</a> and that made me realise the importance of stability in software. I may be taking this one personally because of the time and effort I expended, trying to find that bug, but I still think Hugo is more stable than Gatsby.</p>
<h4 id="heading-security">Security</h4>
<p>Gatsby uses JavaScript, and JavaScript applications are notorious for requiring a lot of Node modules to run. There is even a Node module that <a target="_blank" href="https://medium.com/@jdan/i-peeked-into-my-node-modules-directory-and-you-wont-believe-what-happened-next-b89f63d21558">sends Hot Pocket tweets</a> and another that <a target="_blank" href="https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5">harvests credit card numbers</a> :D. Static sites tend to be more secure by nature, but I still think it is worth mentioning that more dependencies result in more code that you might not trust.</p>
<h4 id="heading-tooling">Tooling</h4>
<p>Gatsby has all the advantages of the <a target="_blank" href="https://www.npmjs.com/search?q=Gatsby">JavaScript toolchain</a> and all the disadvantages of <a target="_blank" href="https://medium.com/@ericclemmons/javascript-fatigue-48d4011b6fc4">JavaScript fatigue</a>. On top of that, Gatsby has a really nice <a target="_blank" href="https://www.gatsbyjs.org/plugins/">plugin library</a>. In particular, <a target="_blank" href="https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-offline">gatsby-plugin-offline</a> allowed me to easily add offline capabilities to my website, which I still haven’t figured out with Hugo.</p>
<p>On the other hand, some things that require a plugin with Gatsby just come out of the box with Hugo. For example, <a target="_blank" href="https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-react-helmet">gatsby-plugin-react-helmet</a> is needed to edit the head tag, whereas this can be done with simple HTML in Hugo. Since I enjoyed using the tooling that came with Gatsby, I give this one to Gatsby.</p>
<h4 id="heading-build-speed">Build speed</h4>
<p>Hugo is able to build my website without any additional tooling in less than 100ms. Gatsby is able to build my website in about 15 seconds, but this does include a lot of additional tooling. Adding <a target="_blank" href="https://github.com/postcss/postcss">PostCSS</a> and <a target="_blank" href="https://github.com/imagemin/imagemin">Imagemin</a> to the Hugo build bumps the build time up to about 5 seconds. Watching for changes during development was also faster using Hugo. I think Hugo is the winner here.</p>
<h4 id="heading-documentation">Documentation</h4>
<p>Both Gatsby and Hugo have really nice documentation. Hugo has a <a target="_blank" href="https://gohugo.io/getting-started/quick-start/">Quick Start</a> and Gatsby has a <a target="_blank" href="https://www.gatsbyjs.org/docs/">Getting Started</a> section. Gatsby also has a really nice <a target="_blank" href="https://www.gatsbyjs.org/tutorial/">tutorial</a>, which evens out the steeper learning curve. Personally, I found it easier to get started with Gatsby, but that is because I already understood React. I think it is fair to say that both Hugo and Gatsby have great documentation.</p>
<h4 id="heading-performance">Performance</h4>
<p>Using <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/">Lighthouse</a>, the performance score was 100 for my site in Hugo and 95 for my site in Gatsby. The First Contentful Paint for a 3G connection was about 1 second for the Hugo site and 1.5 seconds for the Gatsby site. Using <a target="_blank" href="https://www.webpagetest.org/">Web Page Test</a> the load time on a 2G connection was <a target="_blank" href="https://www.webpagetest.org/result/180722_Y6_19710626850f2326f4610b156398dbf0/">8.7 seconds in Hugo</a> and <a target="_blank" href="https://www.webpagetest.org/result/180722_PJ_fa010df1c51f603586ee9c04f2abd558/">11.7 seconds in Gatsby</a>.</p>
<p>However, doing a simple manual test to see which site loads first, Gatsby was noticeably faster, so I don’t really understand what Lighthouse or Web Page Test was measuring. Furthermore, as Gatsby is a Single Page App, navigating within the website does not require a request from the server. Pages are just re-rendered with JavaScript. Anyhow, I can say with certainty that both Hugo and Gatsby are fast. I would be interested to hear your thoughts in the comments below.</p>
<h4 id="heading-community">Community</h4>
<p>Gatsby is gaining popularity quickly, which comes with a thriving community. That is not to say that Hugo’s community is boring. If GitHub stars are anything to go by, Hugo has more than 27 thousand and Gatsby has more than 23 thousand. On Twitter, Gatsby seems to be more active than Hugo.</p>
<h4 id="heading-final-thoughts">Final thoughts</h4>
<p>So which one should you choose? Gatsby uses React, which I am more familiar with, it has better tooling, and it has a thriving community. On the other hand, Hugo is more stable and spends less time building. For larger websites, build speeds become more important and some of you might not care for React at all. In my case, stability was the most important criteria, so I decided to stick with Hugo. I am very excited to see what the future brings in this space.</p>
<hr>
<p><strong>Before you go…</strong> Thanks for reading the article! I write about my professional and educational experiences as a self-taught software developer, so check out <a target="_blank" href="https://www.learnitmyway.com/">my blog</a> or subscribe to <a target="_blank" href="https://learnitmyway.com/newsletter">my newsletter</a> for more content.</p>
<p><strong>You might also like:</strong></p>
<ul>
<li><a target="_blank" href="https://learnitmyway.com/how-i-release-updates-to-my-personal-website/">How I release updates to my personal website</a></li>
<li><a target="_blank" href="https://www.learnitmyway.com/2016/11/11/learning-material-software-development/">Learning material — software development</a> (a list of learning resources, starting with Introduction to Computer Science)</li>
<li><a target="_blank" href="https://learnitmyway.com/opinion-full-stack/">Is full-stack web development worth learning?</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
