Set Up a Jamstack Develop, Build, and Release System Using GitHub, Hugo, and Netlify

Jamstack is the new standard architecture for the web. Using Git workflows and modern build tools, pre-rendered content is served to a CDN and made dynamic through APIs and serverless functions. Technologies in the stack include JavaScript frameworks, Static Site Generators, Headless CMSs, and CDNs.

Traditional Web (left) vs Jamstack (right)

Jamstack benefits

Jamstack offers many benefits over traditional web stack.

  • Security
    Published text files are HTML, CSS and JavaScript only. No server-side scripts like PHP
  • Scalability
    As website traffic grows, scaling is much easier when your website amounts to a bunch of static HTML files that can be served from a CDN.
  • Performance
    Static HTML files don’t require server-side processing. Files can be served from a CDN.
  • Maintainability
    It’s much easier to maintain a simple server that hosts static files rather than application and database servers. The heavy lifting of building the static files is done before deployment resulting in stable production files.
  • Portability
    Since Jamstack sites are pre-generated, the production files are simple static files which can be hosted anywhere on any simple host.
  • Developer experience
    Jamstack sites are built on widely available tools and conventions making it easier for developers to learn and develop.

Learn more

Jamstack is a term that was coined by two developers who pioneered the architecture while working at Netlify. This post will explain how to set up a web development, build and release system using the following components

  • Hugo (static site generator)
  • GitHub (version control)
  • Netlify (CI/CD + serverless web hosting)
  • Netlify CMS (content management system)
  • imageKit.io (image optimization and CDN)

Web development workflow

Let’s say you have a website at example.com. This workflow will depend on having a GitHub repo with 2 branches (main and develop) and 2 sites hosted on Netlify, one for production (www.example.com) and one for staging (staging.example.com). The staging site will have site-wide password protection using an option in Netlify.

The workflow using this setup is very common:

  1. Edit web page files locally, e.g. using VisualStudio Code
  2. Commit and push changes to develop branch in private GitHub repo
  3. Any commit to the develop branch in GitHub auto-triggers a build to generate and deploy static files to a staging site in Netlify (staging.example.com)
  4. Preview changes in a password-protected, external staging URL
  5. Merge and push changes from develop branch to main branch. This will auto-trigger a build to generate and deploy static files to a production site in Netlify
  6. View published changes in production (www.example.com)

Note

  • If you need to make an edit, you can make it directly in GitHub as well.
  • If you’d like to provide a CMS for your pages (usually for non-technical people), you can use Netlify CMS.

Create GitHub repository and branches

Let’s first create a GitHub repo. We’ll call it example.com and make it private since we don’t want competitors seeing our potentially confidential information. Within this repo, we’ll have 2 branches.

  • main (for www.example.com / production)
  • develop (for staging.example.com / development)

GitHub’s default branch is called main so we’ll use that name for the production branch.

Install Hugo

Since I’m writing this post on a Windows 11 laptop, I will follow the instructions here. For other OSs, follow the instructions here.

  1. Create some folders (C:\Hugo\Sites and C:\Hugo\bin)
  2. Download the Windows executable from the Hugo Releases page (in my case, I’ll go with the extended version to make sure I have all the features I may need – hugo_extended_0.97.3_Windows-64bit.zip)
  3. Extract the executable to C:\Hugo\bin. You will end up with 3 files like this.
  1. Add the executable to the PATH
    1. open Windows PowerShell
    2. change to the C:\Hugo\bin folder
    3. append C:\Hugo\bin to the existing PATH by entering
      $Env:PATH += ";C:\Hugo\bin"
  1. Verify the update by outputting the PATH value using the command
    Write-Output $Env:PATH
  1. Verify the executable by running the command hugo help. You should see output like below.

Create a new site

Now that Hugo is installed, we can use it to create a new site. Run the following commands.

cd C:\Hugo\Sites
hugo new site example.com

You should then see output indicating a new site was created.

In the Hugo/Sites folder, you should see files and folders as shown below.

Here’s what the folders are for.

├── archetypes (templates for different content types)
├── config.toml (top level configuration file)
├── content (this is where content goes in HTML or markdown format)
├── data (for local and remote dynamic content, e.g. JSON data)
├── layouts (layouts for pages (list pages, home page) and partials, etc)
├── static (images, CSS, JavaScript, etc)
└── themes (for themes)

Install a theme

Now, you can browse themes, install one and make customizations to it. In my case, I will create a theme from scratch.

Create a new theme

Run the command hugo new theme exampleTheme to create a theme called “exampleTheme”.

C:\Hugo\Sites\example.com> hugo new theme exampleTheme
Creating theme at C:\Hugo\Sites\example.com\themes\exampleTheme
C:\Hugo\Sites\example.com>

This will create an exampleTheme subfolder in the themes folder. The folder structure should look like this:

.
├── archetypes
│   └── default.md
├── config.toml
├── content
├── data
├── layouts
├── resources
│   └── _gen
│       ├── assets
│       └── images
├── static
└── themes
    └── exampleTheme
        ├── LICENSE
        ├── archetypes
        │   └── default.md
        ├── layouts
        │   ├── 404.html
        │   ├── _default
        │   │   ├── baseof.html
        │   │   ├── list.html (for the list page / index of blog posts)
        │   │   └── single.html
        │   ├── index.html (home page)
        │   └── partials
        │       ├── footer.html
        │       ├── head.html
        │       └── header.html
        ├── static
        │   ├── css
        │   └── js
        └── theme.toml

Note the following files:

  • themes/exampleTheme/layouts/index.html
    this is the home page layout
  • themes/exampleTheme/layouts/single.html
    this is the layout for a single page type, e.g. blog post
  • themes/exampleTheme/layouts/list.html
    this is the layout for a list of page types, e.g. blog posts

When you start development, you want to run hugo server to have Hugo detect file changes and reload your local browser for you. In the output below, we see that the local server is at http://localhost:1313. If I go to that URL, I will see a blank screen since I haven’t created any web pages yet.

Notice also in the output that there are some warnings because we’re missing some files. They will be fixed.

Edit config file

I’m using VisualStudio Code to edit files. First, I’ll update the config.toml file by changing the base URL and title. In order to use the new theme we created, we need to reference it in the config file by adding theme = 'exampleTheme' to it.

Most websites have a main menu for navigating from one page to another. The menu links are common to all pages. Let’s define them in the config file by adding the following to it.

[menu]
  [[menu.main]]
    name = "Home"
    url = "/"
    weight = 1
  [[menu.main]]
    name = "Posts"
    url = "/posts/"
    weight = 2
  [[menu.main]]
    name = "Tags"
    url = "/tags/"
    weight = 3

Layouts & partials

If you open the theme’s layouts folder, you’ll see some default layouts and some partials layouts. The base layout (baseof.html) is the parent-most layout. As you can see in the screenshot below, the base layout has

  • some partials
  • a main block

The list (list.html) and single (single.html) layouts will use the base (baseof.html) layout. The contents of the list and single layouts will go in the main block of the base layout.

The base layout includes partials for the

  • head section (head.html)
  • header section (header.html)
  • footer section (footer.html)

of every page. Partials are like include files. They are small, context-aware components that can be used economically to keep your templating DRY. These are standard sections of most websites which is why they’ve been auto-generated. If you don’t want to use a particular section, you can remove it from the base layout or leave it as is as those partial files are empty by default.

Now, let’s fill out the layouts and partials.

head.html partial

Open example.com/themes/exampleTheme/layouts/partials/head.html in a text editor. This will be the place for metadata like the document title, character set, styles, scripts, and other meta information. Paste the following

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="/css/style.css">
    {{ $title := print .Site.Title " | " .Title }}
    {{ if .IsHome }}{{ $title = .Site.Title }}{{ end }}
    <title>{{ $title }}</title>
</head>

There are two CSS files. We can create an empty style.css file and put it at example.com/themes/exampleTheme/static/css.

For the page title, we will show the site title (taken from config.toml) if we are on the home page, otherwise, we’ll show the site title followed by the page title of the page we’re on.

header.html partial

Normally in a website’s header you will find navigation links. Open example.com/themes/exampleTheme/layouts/partials/header.html  and paste the following

<div id="nav-border" class="container">
    <nav id="nav" class="nav justify-content-center">
        {{ range .Site.Menus.main }}
        <a class="nav-link" href="{{ .URL }}">
        {{ $text := print .Name | safeHTML }}
        {{ $text }}
        </a>
        {{ end }}
    </nav>
</div>

Note how the navigation link names and URLs are coming from the config.toml file we updated earlier. The keyword range causes the template to loop over the items in .Site.Menus.main array. Alternatively, you could just hardcode the nav links directly in the header.html file.

footer.html partial

For the footer, let’s just add a basic copyright disclaimer.

<p class="footer text-center">Copyright (c) {{ now.Format "2006"}} Example.com</p>

The current year will be displayed automatically. If you are wondering why “2006”, you can find out more about it here.

script.html partial

Most websites have some JavaScript. The Hugo auto-generated partial files didn’t include a script partial for JavaScript that should be loaded on all pages. Let’s create one at example.com/themes/exampleTheme/layouts/partials/script.html and paste the following code. Later on, we can add other scripts like jQuery to it.

<script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js" integrity="sha256-0rguYS0qgS6L4qVzANq4kjxPLtvnp5nn2nB5G1lWRv4=" crossorigin="anonymous"></script>
<script src="script.js"></script>

Like style.css, we’ll also need to add a script.js file and put it at example.com/themes/exampleTheme/static/js.

metadata.html partial

Let’s create one more partial to display metadata about each post, e.g. date and tags. Each blog post will have front matter containing key-value pairs specific to each post, e.g.

---
author: "John Doe"
title: "My First Post"
date: "2006-02-01"
tags: ["foo", "bar"]
---

The keys can be referenced as variables in our new partial. Create a new file example.com/themes/exampleTheme/layouts/partials/metadata.html and paste the following code.

{{ $dateTime := .PublishDate.Format "2006-01-02" }}
{{ $dateFormat := .Site.Params.dateFormat | default "Jan 2, 2006" }}

<time datetime="{{ $dateTime }}">{{ .PublishDate.Format $dateFormat }}</time>
{{ with .Params.tags }}
    {{ range . }}
        {{ $href := print (absURL "tags/") (urlize .) }}
        <a class="btn btn-sm btn-outline-dark tag-btn" href="{{ $href }}">{{ . }}</a>
    {{ end }}
{{ end }}

baseof.html layout

As mentioned earlier, the base layout was auto-generated by Hugo. But, since we created a script.html partial for JavaScript files and code that needs to load on all pages, let’s add that partial to the base layout. Open  example.com/themes/exampleTheme/layouts/_default/baseof.html and add script.html after footer.html so that JavaScript does not block page rendering.

<!DOCTYPE html>
<html>
    {{- partial "head.html" . -}}
    <body>
        {{- partial "header.html" . -}}
        <div id="content">
        {{- block "main" . }}{{- end }}
        </div>
        {{- partial "footer.html" . -}}
        {{- partial "script.html" . -}}
    </body>
</html>

list.html layout

This layout will be used to display a list of blog posts. As explained earlier, the content in this file will define the main block of the baseof.html layout. We’ll create a simple layout for listing blog posts. Open example.com/themes/exampleTheme/layouts/_default/list.html and paste the following code.

{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ range .Pages.ByPublishDate.Reverse }}
<p>
    <h3><a class="title" href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
    {{ partial "metadata.html" . }}
    <a class="summary" href="{{ .RelPermalink }}">
        <p>{{ .Summary }}</p>
    </a>
</p>
{{ end }}
{{ end }}

Note how this layout contains a reference to {{ define "main" }} because we are defining the main block of the base layout. We’re referencing .Summary because we only want to show a summary of each blog post.

single.html layout

This layout will be used to display a single page or blog post. As explained earlier, the content in this file will define the main block of the baseof.html layout. We’ll create a simple single-post layout. Open example/themes/exampleTheme/layouts/_default/single.html and copy and paste the code below.

{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ partial "metadata.html" . }}
<br><br>
{{ .Content }}
{{ end }}

Notice how we are including the metadata.html partial in this layout. Unlike in the list partial, which references the Summary variable, this single partial references the Content variable because we want to show the entire blog post content.

Home page layout

The home page layout is at example.com/themes/exampleTheme/layouts/index.html. Since we’re loading Bootstrap CSS, we can use the Jumbotron component to render a hero section. Paste the following.

{{ define "main" }}
<div id="home-jumbotron" class="jumbotron text-center">
  <h1 class="title">{{ .Site.Title }}</h1>
</div>
{{ end }}

404 layout

Hugo auto-generated an empty 404.html layout in our theme. Since Hugo comes with a default 404, we can delete this one or customize it, if we want.

Write your first blog post

In a terminal, run this command hugo new posts/my-first-post.md.

When you do that, Hugo creates a file with some default front matter.

The template for creating new page types is in the archetypes folder. By default, there’s only one called default.md. It’s a markdown file.

Open the newly created content file at example.com/content/posts/my-first-post.md and add some tags to the front matter followed by some content in either Markdown or HTML. Hugo automatically takes the first 70 words of your content as its summary and stores it into the .Summary variable. Instead, you can manually define where the summary ends with a <!--more--> divider. Alternatively, you can add a summary to the front matter if you don’t want your summary to be the beginning of your post.

---
title: "My First Post"
date: 2020-01-26T23:11:13Z
draft: true
tags: ["foo", "bar"]
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pellentesque eu tincidunt tortor aliquam nulla facilisi cras fermentum odio. A erat nam at lectus urna duis. 

Sed velit dignissim sodales ut eu sem. Lectus urna duis convallis convallis 
tellus. Diam sit amet nisl suscipit adipiscing bibendum est. Sed felis eget 
velit aliquet sagittis id consectetur. Vulputate dignissim suspendisse in est ante in nibh mauris cursus. Morbi quis commodo odio aenean. Mollis nunc sed id semper risus in hendrerit gravida rutrum.

<!--more-->

Ac ut consequat semper viverra nam. Hac habitasse platea dictumst vestibulum 
rhoncus. Amet porttitor eget dolor morbi non. Justo eget magna fermentum 
iaculis eu non. Id eu nisl nunc mi ipsum faucibus vitae aliquet nec. Aliquam 
id diam maecenas ultricies. Non sodales neque sodales ut etiam. Amet massa 
vitae tortor condimentum lacinia quis. Erat imperdiet sed euismod nisi porta. 

Nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Viverra 
suspendisse potenti nullam ac. Tincidunt id aliquet risus feugiat in. Varius 
quam quisque id diam vel. Egestas erat imperdiet sed euismod nisi. celerisque felis imperdiet proin fermentum leo vel orci porta non. Ut faucibus pulvinar elementum integer. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl.

Viewing the site

Open a terminal and run the following from the root folder of your site

hugo server -D

The -D flag means to include content marked as draft. Alternative, you can change draft: true to draft: false in the front matter. When I ran that command, I got an error in the terminal.

Since I knew Hugo would serve the site at http://localhost:1313, I went there to see what Hugo would show in this case. Fortunately, Hugo also shows a descriptive error in the browser as well.

Apparently, I added a reference to the script.html partial but I forgot to create the partial. After creating the partial, I reran hugo server -D and I get the following output showing no errors.

The output says that the web server is at http://localhost:1313. Navigating there shows the site as shown below.

This is the home page
This is the list view (there’s only one post right now)
This is the single blog post detail view

How to Fix a Frozen Samsung Ice Maker

Do a Google search and you’ll find that many people have problems with the ice maker in their Samsung refrigerator. Of all the things Samsung can make, it’s ridiculous that they can’t even make an ice maker that just works without freezing up every 3 months. I have the 28 cu. ft. Food Showcase 4-Door Flex™ Refrigerator with FlexZone™ in Stainless Steel. I went with the counter-depth version. It’s smaller than the full-depth version but more expensive for some reason.

I have no problem with this fridge but as cool as it may look, the ice maker is a joke! I used up my extended warranty to have a “certified” technician come out 3 or 4 times to fix the ice maker (some of them are just clueless!). Anyway, in every situation, the ice maker stopped working because of ice buildup preventing the ice maker from working. One technician (a Samsung technician) said it was because I wasn’t using the original Samsung (overpriced) water filter. So, I put in an overpriced Samsung original water filter and, unsurprisingly, the ice maker still stopped working after a while – again, because of ice buildup. And, of course, I’m past the extended warranty so I have to fix it myself. If your stupid Samsung ice maker stops working because of ice buildup, here’s how you fix it (until it stops working again in 3-6 months).

Buy a steam cleaner

I bought this Bissel steam cleaner on Amazon. It’s supposed to be used for cleaning but it works well for melting ice that jams your stupid Samsung ice maker.

Melt the ice

Put some water in the steamer, wait till it’s hot, then stick the nozzle up the ice maker opening and pull the trigger. Super hot steam will fill up the ice maker box and begin to melt any ice in there.

After a while, try to pull out the ice box. If it still doesn’t come out, repeat until it does. Once it comes out, you’ll see some ice buildup like in the picture below.

Use the steamer to target the ice buildup until you can remove all the ice.

In the picture below, you can see that most of the ice is no longer stuck. Once you’ve removed all the ice, put the ice box back in and wait a day for new ice to be made.

And that’s how you temporarily fix your stupid Samsung ice maker.

Auto-Publish a Static Website Using GitHub and Netlify

Let’s say you want to build a simple, static website with the following requirements.

  • You want to keep track of changes to each file using version control.
  • You don’t want to spend a lot of money.
  • You want a simple setup, intuitive interface, and automatic publishing of changes.
  • You want SSL encryption with a certificate that automatically renews.
  • You want your own custom domain.
  • You don’t want to manage servers and you want to focus on your content.

You can accomplish this in many ways. But, I think the easiest way is to use GitHub and Netlify.

GitHub

GitHub, Inc. is a provider of Internet hosting for software development and version control using Git. It offers the distributed version control and source code management functionality of Git, plus its own features.

Netlify

Netlify is a San Francisco-based cloud computing company that offers hosting and serverless backend services for web applications and static websites.

This post will show you how easy it is to set up a simple website using GitHub and Netlify.

1. Create a free GitHub and Netlify account

This step is self-explanatory. If you create a GitHub account first, you can then log in to Netlify using your GitHub account, which saves time.

2. Create a repository in GitHub

I created a private repository (repo) called “documentation” to create a hypothetical website containing product documentation. When I created the repo, I opted to automatically create a README.md file to explain what the repo is about. At this point, there’s only one file in the repo.

3. Create a new project in Netlify

When you create a new project, you have a few options. In this case, we’ll choose “Import from Git”,

4. Choose Git provider

Since we’re using GitHub, choose GitHub.

5. Connect Netlify to GitHub

In order to auto-publish from GitHub to Netlify, you need to connect the two.

6. Choose repositories

You may choose to connect Netlify to all of your GitHub repositories or just some. In this example, I just want Netlify connected to the one “documentation” repo I just created.

7. Choose a branch to deploy

In this example, I only have one default branch called “main” in the “documentation” repo. Since I just need to transfer static files in GitHub to Netlify, there is nothing to build.


8. Deploy site

Once you click the “Deploy Site” button, Netlify will take all existing files in your GitHub repo and host them. You can see the status of the deployment in the “Site overview” tab along with the default URL. Notice that in steps 2 and 3 of the screenshot below, you can set up a custom domain and secure your site automatically with a Let’s Encrypt certificate.

9. See the deployed site

If you go to the auto-generated URL, you’ll see a Page Not Found error. That’s because we only have a README.md file in the repo.

10. Create an index.html file

Let’s create a home page for our new website. In GitHub, create a new file with filename “index.html” and add some HTML to it.

Commit the new file. For simplicity, I will leave the default values and click “Commit new file”.

You will then see that you have two files in the repo.

11. View deployment status

When you committed the file in GitHub, that automatically triggered a deployment to Netlify. In Netlify, click the “Deploys” tab to check the status of the deployment. In the screenshot below, we see that the status is “Published”.

12. View the deployed change

Going to the auto-generated URL in Netlify, we see that our new home page has been published.

13. Handling Large Media (Images, PDFs, etc)

If you have large, non-text media such as images and PDFs, to avoid bloating your repo, you can use Git LFS. Clicking on the “Large Media” tab gives you more information about that. Personally, for images, I prefer to use ImageKit.io. Alternatives are Cloudinary and Uploadcare.

14. Analytics

You can install Google Analytics on your website and/or you can get analytics from Netlify for $9 per month. Clicking on the “Analytics” tab provides more information.

Redirects

If you need to create redirects, you can use a simple redirect file.

Or, you can create advanced redirects in the Netlify configuration file.

Learn more about redirects.

Access Control

If you want a staging environment where certain people can preview changes before you push them to production, one way to do this is by

  1. creating a “develop” branch in your GitHub repo
  2. connecting Netflify to that branch
  3. enabling site-wide password protection in Netlify (password protection is a paid feature) on the site sourced from that branch.

Though Netlify supports basic authentication, it is not the most secure method of protecting a site. Instead, you can use Netlify’s site-wide protection which uses JWT secret.

Learn more

Quick ‘n Easy, High-Protein, Low-Calorie Eggs (Whites) Recipe

Eggs are some of the healthiest foods you can eat. And, they taste good and are very cheap. However, if you’re looking to lose weight while maintaining or building muscle mass, you probably should only eat egg whites instead of whole eggs. As you can see in the table below, most of the protein in a whole egg comes from the egg white and all of the fat comes from the yolk.

Egg WhiteWhole Egg
Calories1871
Protein4 grams6 grams
Fat0 grams5 grams

You can buy egg whites cheap at Costco. A box of 6 16oz cartons costs $9.

Cooking it is super simple. I prefer the set-and-forget method to create an egg white patty. Here’s the recipe.

  1. In a small pan, spray non-stick cooking oil.
  2. Pour in the equivalent of 4 egg whites.
  3. Add salt and pepper to taste.
  4. Cover with lid
  5. Set timer to 3 minutes
  6. Cook on medium heat

Using a Spreadsheet To Plan a Trip

Planning a trip can be fun, but it can also be challenging. With so many activities to plan for, having a high-level view of a month-long or two-week-long trip makes it easy to see the bigger picture, but a certain amount of detail is necessary to ensure your planned itinerary is reasonable and makes sense, e.g. if it takes 2 hours to get to the airport and you have to be at the airport 3 hours before departure, then you better take that into account so you don’t miss your flight. One simple way to plan a trip is by using a spreadsheet like Google Sheets. I’ve created a hypothetical San Francisco, USA to Seoul, Korea vacation plan showing day-by-day and hour-by-hour activities in two different views.

Of course, you can group or split each time block to have less or more time slots depending on your needs.

Click each example below to enlarge.

Example Month Schedule
Example 2 Week Schedule

Easily Find Cheap Flights Using Price Grids

Plane ticket prices can vary significantly from one day to another and by airline, number of stops, total trip duration, etc. If your dates are flexible, you can easily find cheaper flights using Kayak.com and Google Flights’ price grids. Here’s an example. Say I want to fly from San Francisco to Korea some time in early May.

  • I want no more than 1 stop / layover
  • I don’t want a layover longer than 4 hours

Kayak

On Kayak.com, you need to select +- 3 days for each departure and return date.

Choose some filters.

After the search results appear, expand the Flexible Dates section to show the price grid.

What’s nice about Kayak is, when you hover over a price, you see

  • the total price
  • the airline
  • the number of stops
  • the total flight plus layover time, if any, for both departure and return
  • the total trip duration

In the example above, the $1339 price offers nonstop service for a 9-day trip. If we compare that to the cheapest price ($1095), we’d have to stop somewhere and the total trip duration would be 11 days.

Google Flights

On Google Flights, you can do a similar search. For round trip flights, there will always be a price grid so you just enter your preferred departure and return dates. For the filters, I chose “1 stop or fewer” and “Under 24 hr”.

What’s interesting, and very useful, about Google Flights is, once you choose a departure date, prices start to show up in the date picker so you can choose a return date that is cheaper than others.

Google Flights is really fast. The prices and search results show up almost instantly.

On Google Flights, the cheapest prices are highlighted in green cells. In this example, it’s $1050. Compared to Kayak, it looks like Google Flights has more flight options as all of the cells have a price. However, unlike Kayak, in order to see more information like the airline, trip duration, and number of stops, you have to choose a price and click OK. The price grid will disappear and you will see the flight details.

Which is cheaper?

If we put both grids next to each other, we find that

  • Google Flights offers more flights (all cells have a price)
  • Google Flights offers either the same price or cheaper prices, even if the price difference is just $1.
Kayak
Google Flights

Note

Kayak can book flights for you whereas Google Flights will always send you to an external booking website. Price differences are often due to different travel websites offering different prices. It’s always preferable to book directly with the airline as that cuts out any intermediaries and, if there are any issues, you can contact the airline (or Kayak) directly and usually get better customer service. Just because Google Flights may offer cheaper flights, you might still find an overall better deal on Kayak.

How To Add a 240V Electrical Circuit to Your Breaker Panel

The article is based on this YouTube video. These instructions are similar for adding 120 V circuits as well.

When you open the panel door, verify that you have empty slots to put a one-pole (120 V) or two-pole (240V) circuit breaker. If you don’t, you may need to upgrade your panel to a larger one.

Unscrew the screws and remove the panel cover.

You will usually see two large cables coming into the box.

  • One black wire = + 120
  • Other black wire = – 120
  • Voltage diff between the two = 240

The 2 black wires go into a main breaker.

In this example, the main breaker supports 125 amps.

The electricity then goes into the two left and right rails. Usually you’ll have some 120 V / 15 A circuit breakers for general power, 120 V / 20 A circuit breakers for kitchen and bathroom power, and 240 V / 20 A double circuit breakers for an electric dryer.

Electricity flows from the rail through the circuit breaker through the wire.

Everything in the box is hot until you turn off the main breaker. Once you turn off the main breaker, only the 2 black wires and the terminals they are connected to are still hot.

Parts Needed

Halex 3/4 in. Non-Metallic (NM) Twin-Screw Clamp Connectors (5-Pack)

Southwire (By-the-Foot) 6/3 Stranded Romex SIMpull CU NM-B W/G Wire

The max amperage that this cable supports is 55 amps.


50 Amp 2-Pole Circuit Breaker

Find the right kind that fits your circuit breaker box. My breaker box brand is Challenger. From the time when Challenger went out of business in the 90s, they were bought by different companies until Eaton/Cutler-Hammer finally got a hold of them. So, in my case, I can buy Eaton BR/C breakers.

The amperage of the circuit breaker must be less than or equal to the max amperage of the cable connected to it to prevent the cable from melting and causing a fire.

2-Gang 4 in. Square New Work Electrical Wall Box

Buy a deep box to accommodate the depth of the receptacle and have space for the 4 wires.

Legrand Pass & Seymour 50 Amp 125/250-Volt NEMA 14-50R Flush Mount Range/Dryer/EV Charger Power Outlet

The outlet will go in the middle of the electrical box.


Leviton White 2-Gang Single Outlet Wall Plate (1-Pack)

Decide where you want the new cable to come into the breaker box and punch out a hole at that location.

You can use a hammer to push the cable clamp into the hole you just made.

If you will run your cable behind the wall, make a hole in each stud using a ¾” spade bit for the cable to go through.

If you will run your cable on the wall, you can run it through metallic or non-metallic PVC (rigid and flexible)  electrical conduit.

JM eagle 3/4 in. x 10 ft. PVC Schedule 40 Conduit

You can then connect the cable to a surface-mount NEMA 14-50R power outlet.

Leviton 50 Amp Single Surface Mounted Single Outlet, Black

Run the cable through the stud and into the breaker box.

The cable can be hard to cut. If you can’t cut it with scissors or snips, you can use an angle grinder.

For ease of work, feed the thick wire into the electrical box before attaching box to stud.

Strip the wires in the cable.

Attach the wires to the outlet. The red and black hot wires are interchangeable.


And screw the outlet to the box.

Strip the wires on the other end of the cable. Make sure the main circuit breaker is off.

  1. Connect the white (neutral) wire to the neutral bar.
  2. Connect the bare copper (ground) wire to the ground bar.
  3. Connect the two hot (red and black) wires to the 240V circuit breaker.
  4. Insert the circuit breaker.
  5. Turn on the main breaker and then turn on the new 240 V circuit breaker.
  6. Test voltage
  7. Hot (red) to hot (black) should be about 240 V.
  • Hot (red) to neutral should be about 120 V.
  • Hot (black) to neutral should also be about 120 V.

Do the same voltage test at the receptacle.

You can buy this EV charger cable on Amazon for $330.

Morec EV Charger 32 Amp NEMA14-50 SAE J1772 ev Charging Cable Level 2 Portable Electric Vehicle Charger 220V-240V 26ft (7.9M), Compatible with Most Electric Vehicle Cars

Seoul, South Korea: Attractions, Food and More

This is not a complete list of tourist places and popular Korean food. It’s just a list of places that I think may be worth visiting and a few other relevant things.

Korean Air

  • Website
  • As of December 30, 2021, all travelers, including fully vaccinated travelers, must submit a negative PCR test taken within 48 hours of departure in order to board flights to the US. Learn more.
  • As of March 2, 2022, foreigners must submit a negative PCR test taken within 48 hours of departure in order to board flights to Korea. Read consular notice.
  • Starting from April 1, 2022, vaccinated travelers who have completed vaccination overseas AND register their vaccination history through the Quarantine COVID19 Defence (Q-Code) system BEFORE traveling to Korea will be eligible for quarantine exemption. Vaccination is considered to be complete 14 days after the 2nd shot for a two-dose vaccine (valid up to 180 days) or with booster shots. Read consular notice.

Seoul, South Korea

Incheon Airport

Tourist Cards

  • Website
  • Buy 7-day MPass (Metropolitan Pass) card for $53
  • Purchase locations:
    • Incheon Airport Tourist Information Center: 1F, Passenger Terminal 1 in front of Gates 5 & 10 / 7 AM-10 PM
    • Myeong-dong Tourist Information Center: In front of Euljiro 1(il)-ga Station (Seoul Subway Line 2), Exit 5 / 09:00-20:00, Closed on the day of Seollal (Lunar New Year’s Day) & Chuseok (Korean Thanksgiving Day)
    • Seoul Station Tmoney Town : 1F, Seoul City Tower near Seoul Station (Seoul Subway Line 1, 4, AREX), Exit 10 / Weekdays 09:00-18:00
  • There is a $4.11 deposit. Get deposit refund (minus $0.41 for the cost of the card) at the Tourist Information Center at the airport before leaving Korea.
  • MPass can be used on subway lines, airport railroad all-stop train, Seoul city buses (mainline buses, branch buses, circulation buses, village buses, night buses)
  • The pass offers 20 rides a day for the duration of the pass. 

Hotels

When choosing a hotel, if you want to be close to

  • many attractions
  • easy subway transportation with direct connection to the airport
  • tons of food options
  • tons of shopping options

you probably want to stay around the Myeong-dong area.


ibis Ambassador Seoul Myeongdong

KTO Tourist Information Center

Modern, High-Tech Seoul

Meerkat Cafe Seoul

Lotte World

  • World’s largest indoor theme park
  • Website
  • Hours: 9:30AM–11PM except Sundays
  • Admission: $50

Lotte World Aquarium

  • Website
  • Hours: 10AM–8PM except Sundays
  • Admission: $28

Lotte World Mall & Tower

  • Seoul’s largest shopping mall
  • Google Maps
  • Mall Hours: 10:30AM–10PM
  • Tower Hours: 24 hours

Seoul Sky

  • The Seoul Sky Observatory is located at the top of the Lotte World Tower, the world’s fifth tallest building standing 123 stories and 555 meters high. As the tallest building in South Korea, it is the only place where you can take in a gorgeous 360-degree view of Seoul, the capital city of South Korea roaring with brilliant history and dynamic modern culture.
  • Google Maps
  • Website
  • Seoul Sky Hours: 10:30AM–10PM
  • Skybridge Website
  • Skybridge Tour Hours: See timeslots (closed in winter from Dec 13 to March 31)
  • Skybridge Admission: $67 – $93
Skybridge Tour

Dongdaemun Design Plaza

The War Memorial of Korea

Seodaemun Prison History Museum

Seoul City Hall

Noryangjin Fisheries Wholesale Market

Seoullo 7017

Namdaemun Market

  • It is the oldest and largest market in Korea.
  • Website
  • Google Maps
  • Hours: varies by store. Closed on Sundays.

Shinsegae

N Seoul Tower & Namsan

Seoul Grand Park & Skylift

  • Seoul Grand Park is a park complex to the south of Seoul, South Korea, in the city of Gwacheon. Facilities at Seoul Grand Park include hills and hiking trails, Seoul Grand Park Zoo, Children’s Zoo, a rose garden, Seoul Land amusement park, and the Seoul Museum of Modern Art.
  • Website
  • Google Maps
  • Hours: 9AM-6PM (March~April,September~October)
  • Admission:
    • Zoo: $4
    • Theme Garden: $2
    • Skylift: $5
  • Skylift Website
  • Skylift 1 (outside the zoo): 10 minute ride
  • Skylift 2 (inside the zoo): 15 minute ride

Cheonggyecheon

  • Course 1: Cheonggye Plaza to Dongdaemun: 2.7 Km (about 2 hours)
  • Course 2: Cheonggyecheon Museum to Dongdaemun: 2.6 Km (about 2.5 hours)

DMZ

Website

DMZ Tour by Tour Agency

KORIDOOR DMZ Tours

DMZ Tour by Train

  • Website
  • Take the DMZ Peace Train from Seoul to Dorasan.
  • Choose the Dorasan Security Tour.
  • Tour package is available from the KORAIL Travel Center at each departure station
  • Foreigners can purchase train tickets from Yongsan Station to Dorasan Station online, but will have to register for the Dorasan Security Tour at Dorasan Station if they wish to travel further.
  • Operating days: Wednesday-Sunday, once a day / No trains Monday-Tuesday
  • Train Tickets and Times
  • Ticket Price: $30
  • Tour schedule: 
    1. Depart from Yongsan Station (10:08) – Seoul Station (10:15) –
    2. Arrive at Imjingang Station (11:24) –
    3. Complete identity check (11:32) –
    4. Arrive at Dorasan Station (Google Maps) and board connecting bus (11:43) –
    5. Dorasan Peace Park (12:10) –
    6. Lunch break at Tongilchon (Unification Village) (13:00) – 
    7. Dora Observatory (Google Maps) (14:00) – 
    8. The 3rd Tunnel (Google Maps) (14:40) –
    9. Tour Unification Platform (15:50) –
    10. Depart from Dorasan Station (16:27) – Seoul Station (17:47) –
    11. Arrive at Yongsan Station (17:54)

Dora Observatory

Look at North Korea from South Korea

DMZ The 3rd Infiltration Tunnel

Website

Panmunjeom / DMZ Joint Security Area

Lotus Lantern Festival in May

Haewoojae Museum (Mr. Toilet House) (해우재)

Seoul Hop-On Hop-Off Bus

Songdo International City

  • Wikipedia
  • Google Maps
  • Tour
  • Highlights:
    • Songdo Central Park
    • G-tower for panoramic views of Songdo city
    • Canal walk and NC CUBE shopping center

Itaewon

It’s where all the foreigners hang out at night.

Apgujungrodeo

It’s like Rodeo Drive in Beverly Hills.

Myeongdong Street Food

  • Google Maps
  • Hours: 9AM-9PM
  • On Saturdays and Sundays, there are many more street food vendors at night.

Food to try at Myeongdong

Korean Stir-Fried Noodles
Baked Cheese – Ice Cream Waffles
Tornado Potato
Fried Chicken
Omelette Burger
Grilled Lobster
Omelette Pancake
Banana Pancake
Teokgalbi Meatballs

Gyeyeolsa Fried Chicken

Casablanca Sandwicherie

Passion 5 Bakery

Costco

HBC Gogitjib 이태원더고깃집(구HBC고깃집)

Downtowner Cheongdam 다운타우너 청담점

Linus’ BBQ

  • Beef brisket
  • Google Maps
  • Hours: Weekdays: 12-3PM, 5-9PM, Weekends: 12-9PM

Gino’s NY Pizza

Gwangjang Market

  • A traditional street market in Jongno-gu, Seoul, South Korea. The market is one of the oldest and largest traditional markets in South Korea, with more than 5000 shops and 20,000 employees in an area of 42,000 square meters.
  • Website
  • Official Website
  • Google Maps
  • Hours: 10AM-11PM

Jilhal Bros

Hongdae Free Market

Hyundai Department Store | D-Cube City

Seoul Public Bike

  • Official Website
  • Government Website
  • Rent a bike to tour the Cheonggyecheon
  • Pick up and drop off at any Bike Seoul location
  • 1 hour – $0.82
  • 2 hours – $1.64
  • All day – $4.11
  • $0.82 for every additional 30 minutes

Dongdaemun Market

Seoul Folk Flea Market

Starfield COEX Mall

Flying Suwon

Everland Resort

Caribbean Bay

Yeontabal BBQ Restaurant

  • Address: 1317-16 Seocho-dong, Seocho-gu, Seoul, South Korea
  • Google Maps
  • Nearest Subway Station: Gangnam
  • Operating Hours: 11:30AM-10PM, daily
  • What to Order: Grilled king beef ribs
  • Expect to Pay: Around $33-$38 per person

빈대떡 (Bindae-tteok: Mung bean pancake)

Video

호떡 (Hotteok: Korean Sweet Pancakes)

Hottoek is a Korean-styled pancake. One of the most popular Korean street snacks, it is made from a simple flour batter and filled with sweet syrup made from cinnamon, brown sugar, and peanuts. It’s a great snack when you’re between meals.

Video

팥빙수 (Patbingsu: Shaved Ice with Sweet Rea Beans)

Pat Bingsu – Bingsu with red beans

Video

화전 (Hwajeon: Pan-fried Sweet Rice Cakes with Flower Petals)

Video

찐빵 (Jjinppang: Fluffy steamed buns filled with sweet red beans)

Video

경단 (Gyeongdan: Sweet Rice Balls)

Video

황남빵 (Hwangnambbang: Hwangnam Bread)

Red beans are mixed with eggs and wheat dough by hand, then this artisan bread is shaped and baked without any artificial sweeteners or preservatives, keeping only the sweetness of its original natural ingredients.

Video

호두과자 (Hodu-gwaja: Walnut Cookies)

Hodu means walnut in Korean and they are cookies filled with red bean paste and walnuts.

허니 브래드 (Honey Bread)

Thick bread is divided into nine parts filled with whipped cream and then topped with honey, caramel syrup, and cinnamon powder. Its original name was Honey Butter Bread.

계란빵 (Gyeran-ppang: Egg Bread)

has a shape of rounded rectangle and contains whole egg inside of a bread. They are often sold by street vendors.

꽈배기 (Kkwabaegi: Twisted Korean Doughnuts)

It is made with glutinous rice flour and melted butter. The dough is deep-fried in oil and tossed in sugar and cinnamon powder. Like most fried breads, it tastes better when it’s hot. You can enjoy the original taste of twisted bread by eating when it just comes out of the oil, or by heating them up. 

뚱카롱 (Ddungcaron: Fat Macaron)

Rice Punch 식혜 (Korean Sweet Rice Drink)

Songpyeon half-moon rice cake (송편)

The fillings for these rice cakes vary, but the most common are sweetened sesame seeds and mung beans.

Video

Chapssaltteok (Korean style mochi)

Filled with sweet red beans

Korean dalgona coffee

Pat-sirutteok 팥시루떡 (Layered rice cake with red beans)

율란 Yul-lan – Chestnut cookies

무지개떡 Mujigae-tteok (Rainbow rice cake)

불고기 Beef Bulgogi

Thinly sliced or shredded beef marinated in soy sauce, sesame oil, garlic, sugar, scallions, and black pepper, cooked on a grill (sometimes at the table). Bulgogi literally means “fire meat.” Variations include pork (dwaeji bulgogi, 돼지불고기), chicken (dak bulgogi 닭불고기), or squid (ojingeo bulgogi, 오징어불고기).

Galbi (갈비) – pork or beef ribs

Cooked on a metal plate over charcoal in the centre of the table. The meat is sliced thicker than bulgogi. It is often called “Korean barbecue” along with bulgogi, and can be seasoned or unseasoned.

Dak galbi (닭갈비)

Stir-fry marinated diced chicken in a gochujang-based sauce, and sliced cabbage, sweet potato, scallions, onions and tteok.

Dubujeon (두부전)

Steamed tofu mixed with ground beef and vegetables.

Bungeoppang (붕어빵; “carp-bread”)

is the Korean name for the Japanese fish-shaped pastry Taiyaki that is usually filled with sweet red bean paste and then baked in a fish-shaped mold. It is very chewy on the inside and crispy on the outside.

Gukwa-ppang (국화빵)

is almost the same as bungeoppang, but it is shaped like a flower.

Places Not in Seoul

As a bonus, here are a few places in Korea outside of Seoul.

Lotte Waterpark

Free Topic-Based Communication Tool for Small Groups Like HOAs

I’ve used many enterprise-level productivity tools like Atlassian Jira, Confluence (Wiki), Microsoft Teams, Asana, and, of course, email. Asana seems to be the best for managing large projects that have multiple tasks and deadlines. Microsoft Teams is great for having discussions separated by topic and sharing documents related to each discussion. As the president of an HOA (Homeowner’s Association) that pays an experienced property manager, it’s interesting that we’re still communicating by email because so often we’d have a hard time finding specific information and documents. Microsoft Teams would be a big improvement but the free version doesn’t come with some useful features available in the paid version. Of all the tools I’ve used, it looks like Slack fits the bill because 1) there is no bill (pun intended – there’s a free version) and 2) it comes with features similar to the ones in the paid version of Microsoft Teams. This post will explain some of Slack’s features that could be beneficial for small groups like an HOA.

Separate Discussions By Topic

One of the problems with a simple chat tool is different topics get lost in one super long chat. At my HOA, we have different topics to talk about, e.g. landscaping, security cameras, parking, etc. With Slack, you can create multiple channels to represent these topics. Each channel is a separate chat discussion as you can see in the screenshot below.

In the screenshot above, you can see:

  • Group Name: Antoine Ct Landlords
  • Channels: These are discussion topics:
    • landscaping-cleaning
    • security-cameras
    • vehicles-and-parking
  • Direct messages: this shows you who is in the group and allows you to send a message directly to one specific person
  • Apps: you can see the list of apps you’ve integrated with Slack such as a polling app

In the screenshot above, the chat in view is the one for the landscaping-cleaning channel.

Apps

Slack allows you to integrate many apps for a seamless experience. Below are some of the apps you can integrate.

One thing we often do as an HOA is conduct polls. You can add a polling app and then create a poll in a channel. For example, I added the Simple Poll app and created a poll in the landscaping-cleaning channel. In the screenshot below, the simple poll asks if everyone wants to hire one landscaper for all units and split the cost. The answer options are simply yes and no.

Of course, if there is too much chatter, the poll can get buried in the history of chat messages. If that happens, you can pin the poll to the top. It then shows up in the bar at the top of the channel like this:

Chatting is useful, but eventually you’re going to need other productivity tools like documents, spreadsheets, presentations, etc. In the chat field, you can click the + button to insert things other than text, e.g. create a post.

A post in Slack is like a Google or Word doc.

If you prefer to use a different tool like Google Docs, you can link the Google Doc to your Slack channel. Just copy the Google Doc share URL and paste it into the chat and, optionally, pin it to the top as I did for the poll example above. Or, you can create folders in the bar at the top of the channel to organize documents and chat messages. In the screenshot below, I clicked the + button to add two folders: Documents and Photos.

I then hovered over the Documents folder link and clicked Add to bookmark to add links to external resources:

  • Test Document 1 (link to a shared Google Doc)
  • Association Website (link to a WordPress site)

Multiple Teams

If you are part of multiple groups or teams of people, you can create a separate Slack group (called Workspaces). In the screenshot below of the Slack homepage, I see the workspace for the example HOA group mentioned above called “Antoine Ct Landlords”. There is also a button to create a new workspace.

If you are part of a small (or large) group of people and need to discuss many topics and don’t want to pay a monthly fee, you may want to give Slack a try.

Calculate Frequency of Unique Values in Google Sheets

I’m often finding myself needing to calculate the frequency of unique values in a spreadsheet. It turns out it’s a 2-step process. For example, if you have a column of data as shown below and you want to know how many times the numbers 1, 2, and 3 occur, you need to first add a column containing the unique values in column 1. Then, you can use the frequency function to calculate frequency.

1. Get Unique Values

In column 2, get the unique values in column one using the unique function:

=UNIQUE(A2:A7)

If you want, you can also sort the values as follows.

=SORT(UNIQUE(A2:A7))

2. Get Value Frequency

In column 3, get the value frequency using the frequency function. The data is in column 1 and the classes are in column 2.

FREQUENCY(data, classes)

=FREQUENCY(A2:A7, B2:B4)

Google Sheets seems to want to add an extra row with the value 0. I just ignore that.