Vue Static Site Generator with Nuxt and Mardown. Let's Create a Serverless Blog. Part 3


This website you are visiting have been created using Nuxt and Markdown and is serving SEO-friendly blog posts without the need of a server. Want to learn how to do it? Keep reading...


Published date: March 27 2020

In Part 1 of this article we talked about what are static site generators and what are teh advantages of using them when working with Javascript frameworks, we learned the basics of Nuxt, created our nuxt project and added a navbar and a footer component, learned how to use sass, and started to create our serverless blog using Markdown and FrontMatter.

In Part 2 we saw how to add internationalization to our static site —including the blog— using Nuxt-i18n"

In this part, I'm going to create the home page of my site. What we have so far is basically the barebones of the page that Nuxt scaffolded for us. Let's make some changes.

If you are only interested in how to build the blog, you can go to Part 4

Or feel free to jump to Part 5 if what you wanna see is how to generate your static site and deploy it to Netlify.

Index

Complete Project Code: You can get the source code on GitHub

Prerequisite: HTML, CSS, JavaScript, Vue.js, and Markdown.

Initial Setup and Adding Google Fonts to a Nuxt Project

I have created some global styles in the styles folder. As this is not a CSS anf HTML tutorial, I'm not going to get into details, but feel free to get the code from the GitHub repo

As I'm using Google Fonts, we have to add them. The Nuxt way of doing it is by adding the link to the fonts to the head object of the nuxt.config.js.

This is the code to add if you wan to to use the same fonts I'm using, which are Montserrat and IBM Plex Mono.

      { 
        rel: 'stylesheet',
        href: 'https://fonts.googleapis.com/css?family=IBM+Plex+Mono|Cutive+Mono|Montserrat&display=swap'
      }
nuxt google fonts

The head property is used to add external resources to the HTML head tags. If you inspect your app with DevTools you will see the link to GoogleFonts in the section

add google fonts to nuxt static blog

Now you can use the fonts as usual. I have added the font-family property to the body and html elements in main.sccs.

static site generator nuxt

Adding decorative elements: animated waves and blob

Our home page is in pages/index.vue, as you might remember.

I'm going to start by creating the header section. I'm going to create the animated waves at the bottom on the heading using pure css and svg.

nuxt markdown blog

As this is something a little special, I'm going to show you how I dis it. So, bear with me if you are interested. Otherwise, feeel free to skip this part.

Inside container, create a new div.

<div id="header" class="waves">
</div>

Now, we need to draw the svg path of the curve we want to draw.

You can use a wage generator tool like GetWaves or draw the path of your curve yourself using something like Mavo As this is not a tutorial about svg or curve drawing, I'm going to just paste here the code for my svg wave and the corresponding css.

<div class="container-fuid waves" id="header">

<svg class="svg-waves" viewBox="0 24 150 28 " preserveAspectRatio="none"> <defs> <path id="wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" /> </defs> <g class="wave-one"> <use xlink:href="#wave" x="50" y="0" fill="#ff4265" /> </g>

<g class="wave-two"> <use xlink:href="#wave" x="50" y="4" fill="#fff" /> </g>

</svg>

</div>

 @import '../assets/styles/main.scss';

#header{
     background-color: $black;
     min-height: 100vh;
     padding-top: 100px;
}

.waves {
  display: block;
  position: relative;
  width: 100%;
  height: auto;
  background: $black;
  & > .svg-waves {
    display: block;
    width: 100%;
    height: 60px;
    max-height: 60px;
    margin: 0;
    z-index: 5;
    bottom: 0;
    position: absolute;
    left: 0px;
    float: left;
  }
}


.wave-one > use {
  animation: move-forever2 16s linear infinite;
  &:nth-child(1) {
    animation-delay: -2s;
  }
}

.wave-two > use {
  animation: move-forever4 10s linear infinite;
  &:nth-child(1) {
    animation-delay: -2s;
  }
}

@keyframes move-forever4 {
  0% {
    transform: translate(-90px, 0%);
  }
  100% {
    transform: translate(85px, 0%);
  }
}

@keyframes move-forever2 {
  0% {
    transform: translate(-90px, 0%);
  }
  100% {
    transform: translate(85px, 0%);
  }
}

Now, in your browser you shouls see something like this.

svg

Before we keep working with the header, let's modify the navigation bar and the language switcher.

The first thing we are going to do is create the blob I'm using across all the website as a distinctive decorative element. blob

I'm not going to use any logo, so I'm going to replace the Logo component with the Blob component. You can delete it and create a new one or rename it. This component will contain a blob and its corresponding animation.

I have generated the blob using BlobMaker/. You can download the .svg file or, as I have done, inspect the page and copy the code for svg element.

blob generator

Add the blob to the template

<template>
<svg class="svg-blob"  viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(230, 200)">
<path
  d="M184.3,-110.4C211,-60.2,185.6,16.2,146.2,64.9C106.8,113.7,53.4,134.8,-11.5,141.5C-76.5,148.2,
  -153,140.3,-192.4,91.6C-231.8,42.8,-234.1,-46.8,-195.9,-103.7C-157.6,-160.7,-78.8,-184.8,0,
  -184.8C78.8,-184.8,157.6,-160.7,184.3,-110.4Z"
  fill="rgba(255, 66, 101, 1)"
  stroke="none"
  stroke-width="0">
</path>
</g>
</svg>
</template>

And create the corresponding css.

 @import '../assets/styles/main.scss';

.svg-blob{ width: 200px; height: 200px; -webkit-animation: blob-animation ease-in-out 12s infinite; -moz-animation: my blob-animationopright ease-in-out 12s infinite; animation: blob-animation ease-in-out 12s infinite; }

@keyframes blob-animation { 0% { transform: translatey(0px) scale(1); } 50% { transform: translatey(-15px) scale(1.05); } 100% { transform: translatey(0px) scale(1); } }

Now, our blob component is ready and we can add it to any of our pages or components. Lets' add it to the home page header element, along with some styling.

<template>
<div>
<!--HEADER-->
<div class="container-fuid waves" id="header">
<div class="d-flex align-items-center justify-content-center inner-wrapper"  >
  <div class="header-msg">
  <p class="greeting">
  <!--Add the component the regular way and add the class "blob"-->
  <blob class="blob" />
  Hi, I'm Sonia <br />
  <span class="smaller" > 
  I code <br>
  ...

</span> </p>

</div> </div>

#header{
   background-color: $black;
    height: 100vh;
    padding-top: 80px;
   & .inner-wrapper {
     padding: 0 10%;
     min-height: 100%;
     & .header-msg {
      margin-bottom: 40px;
      font-family: "IBM Plex Mono", monospace;
      color: $white;
      & .greeting {
        position: relative;
        font-size: 3.5rem;
        line-height: 3.5rem;
        z-index: 5;
        .smaller {
          font-size: 2.5rem;
          line-height: 2.5rem;
        }
      }
      
      & .blob {
        position: absolute;
        top: -15px;
        z-index: -1;
        left: -95px;
      }
     }
   }
}

}

Improving the Navigation Bar and the Language Switcher

You might be wondering 'What about the translated text for the Spanish language?'. Well' I'm going to use the locale files for that, but first I want to focus only in the design.

Before moving to the rest of the home page sections, let's fix the navigation bar and the language switcher.

First thing will be making the nav bar fixed on scroll. For that, in the NavBar component, let's add the Bootstrap's class fixed-top to the nav element. I'll also make some little changes to the style and use the locales on the menu items.

static site generator vue

We'll come back later to the nav bar to add the links. Next thing I wan t to do is to move the language switcher to the navigation bar. Now we have it in the default layout, so let's remove it form there and place it in the the NavBar component, but first we need to import it and register.

<template>
  <nav class="navbar navbar navbar-expand-xl navbar-dark default-color fixed-top">
  <button class="navbar-toggler" type="button"
    aria-expanded="false" aria-label="Toggle navigation"
    v-bind:class=" { 'navbarOpen': show }"
      v-on:click="toggleNavbar">
    <span class="navbar-toggler-icon" v-bind:class="{ 'opened': show }">
      <span></span>
    </span>
  </button>
  <div class="collapse navbar-collapse"
      v-bind:class="{ 'show': show }">
    <ul class="navbar-nav mr-auto main-items">
      ...
    </ul>
    <ul class="navbar-nav ml-auto nav-flex-icons secondary-items">
      ....
    </ul>
    <!--Add the LangSwitcher component to the NavBar component -->
    <LangSwitcher/>
  </div>
  </nav>
</template>

<script>
  // import it
  import LangSwitcher from '@/components/LangSwitcher'

  // register it as a component
  export default {
    components: {
      LangSwitcher
    },

     ...
}
</script>

And some style:


  @import '../assets/styles/main.scss';

nav { background-color:$black; font-family: ‘IBM Plex Mono’, monospace; font-size: 18px; text-transform: uppercase; letter-spacing: 2px; border-bottom: 1px dashed #1f1f1f;
}

.nav-item{ a{ color: $white!important; display: inline-block; &:hover { color: $base-color!important; } } }

.main-items{ .nav-item:not(:last-child){ color: $white!important; &::after { content: "."; } } }

.secondary-items{ &::after { content: " . "; color: $white; margin: 8px; } }

}

I have also added some style to the language switcher.

<!-- add the btn-lang class to the button  -->
 <button class="btn btn-lang"
              v-bind:class="{'selected-lang': lang.code === $i18n.locale }"
              v-for="(lang, i) in $i18n.locales" :key="`Lang${i}`"
              @click="changeLanguage(lang.code)">{{lang.name}}
</button>
  @import '../assets/styles/main.scss';

.lang-switcher { display: flex; flex-direction: row; justify-content: center; }

.btn-lang { font-family: ‘IBM Plex Mono’, monospace; display: inline-block; border-radius: 0px; text-decoration: none; margin-top:5px; padding: 0 5px; color:$grey-med;

  font-size: 14px;
  text-transform: none;
  letter-spacing: 2px;

}

.selected-lang { color:$base-color; }

Then, I made some changes to the responsive menu in order to make it full width and animate the the open/close icon. Here is the code.

<!--bind the opened class to the show variable-->
<nav class="navbar navbar navbar-expand-xl navbar-dark default-color fixed-top">
<button class="navbar-toggler" type="button"
  aria-expanded="false" aria-label="Toggle navigation"
  v-bind:class=" { 'navbarOpen': show }"
  v-on:click="toggleNavbar">

<span class=“navbar-toggler-icon” v-bind:class="{ ‘opened’: show }">   <span></span> </span> …

</nav>

And in the JavaScript, add the show variable and the method that will toggle its value depending on whether the menu is openned or not


 export default {
    ...
data() {
  return {
    show: false
  }
},

methods: {
  toggleNavbar() {
    this.show = !this.show;
  }
}

}

responsive menu

And the css:

    .navbar-toggler-icon {
        background-image: none!important;
        margin: 1em;
        width: 40px;
        margin: 10px;
        &::after, &::before, span{
                background-color: #fff;
                border-radius: 3px;
                content: '';
                display: block;
                height: 2px;
                margin: 7px 0;
                transition: all .2s ease-in-out;
      }
      &.opened::before{
        transform: translateY(9px) rotate(135deg);
      }
      &.opened::after{
          transform: translateY(-9px) rotate(-135deg);
      }
      &.opened{
        span {
          transform: scale(0);
        }
      }
    }
    .navbar-toggler{
      border: none;
      outline: none;
      .navbar-toggler-icon{
        color: $white!important;
        &:hover {
           color: $base-color!important;
        }
      }
  }

It is pretty straight forward, just notice that I'm creating the hamburger menu via css and animating it.

nuxt boostrap responsive menu

Making it look good in small screens

I have made some adjustments to the home page style to make the nav bar and the header section look good on tablets and phones. Here is the code. I'm not going to go over it since there is nothing special in it.

/* pages/index.Vue /*

/* MEDIA QUERIES */ @media (max-width: 991.98px) {    #header { padding-top: 100px; & .inner-wrapper { & .header-msg { & .greeting { font-size: 2.6rem; line-height: 2.6rem;

      .smaller {
        font-size: 2.4rem;
        line-height: 2.4rem;
      }
    }

    &amp; .message {
      font-size: 1.5rem;
      line-height: 1.7rem;
      margin-top: 50px;
    }
  }
}

}

.wrap { padding: 70px 10% 40px 10%; } }

@media (max-width: 600px) {   #header { padding-top: 80px; & .inner-wrapper { padding: 10px 20px; & .header-msg { margin-bottom: 50px; & .greeting { font-size: 1.6rem; line-height: 1.6rem;

      .smaller {
        font-size: 1.5rem;
        line-height: 1.5rem;
      }
    }

    &amp; .message {
      font-size: 1.1rem;
      line-height: 1.2rem;
      margin-top: 30px;
    }

    &amp; .blob {
      top: -40px;
      left: -15px;
      width: 120px;
    }
  }

}

}

.wave-one > use { animation: move-forever2 12s linear infinite; &:nth-child(1) { animation-delay: 3s; } }

.wave-two > use { animation: move-forever4 18s linear infinite; &:nth-child(1) { animation-delay: -2s; } }

}

/* main.css */

/* MEDIA QUERIES */
@media (max-width: 500px) {
   .btn {
    margin-top: 20px;
    padding: 20px 20px;
    font-size: 18px;
    text-transform: uppercase;
    letter-spacing: 4px;
  }
}
/* navbar.vue */

/* MEDIA QUERIES */
 @media (max-width: 991.98px) {
  .navbar{
     padding:0!important;
     font-size: 30px;
     .navbar-collapse{
       height: 100vh;
       .main-items, .secondary-items{
          text-align: center;
       }

       .main-items{
          &::after {
            content: " . ";
            color: $white;

        }
        .nav-item:not(:last-child){
            &::after {
                  content: "";
                }
        }
      }
     }
  }

 }


@media (max-width: 500px) {
 .navbar{
     padding:0!important;
     font-size: 20px;
 }
}


A couple of things to consider when injecting text from the locales

Now, before adding the rest of the section to our home page, let's take care of the translation of the header section.

Here there is one thing we should pay attention to. Some of the pieces of text we want to translate include html tags, like this one.

nuxt localization markdown blog

There are two things we can do:

a) Create one translation for each piece of text and replace it in the vue file with the  {{ $t('header.greeting-hello') }}  syntax as we have already done in the navbar.

b) We can create larger pieces of translated code including some html tags, for example: " I code. <br> I design. <br> I solve problems. <br>" In that case, as we want to render html tags, we have to use a slightly different syntax using the v-html directive.  <span class="smaller" v-html="$t('header.greeting-do')">

Another problem you'll face when storing the translations in a json object is that you can not store strings in multiple lines, what can affect long strings readability.

A trick you can use, is store your long strings as arrays.

json string in multiple lines

Then, as you need to render each of of the items of the array, you can use something like this.

 <span v-html="$t('header.message[0]')"> </span>
 <span v-html="$t('header.message[1]')"> </span>
 <span v-html="$t('header.message[2]')"> </span>

One note about injecting styles from the localization file: as you can see, in the last item of the array, the text includes a class. When injecting html to an element using the v-html directive, the classes are not rendered if the style is scoped. Keep this in mind and move the underline class to a not scoped style, for example to main.css.

The contact button and other final touches

Next, let´s create the rest of the sections of the home page. I'm not using components for the home page sections because they are not reusable sections and there is not an excessive amount of code, so the number of lines of code in the template does not become excessive. There is nothing special here, just plain html and css, so I'm not going to explain anything. Feel free to check and use the code in the GitHub repo

Finally, let's add the links to the nav bar and fix the contact buttons.

For the links, just regular same page links. Note here that I have added a click event to toggle the navbar when we click on a menu item. This way the nav closes automatically after navigating too a clicked ink.

         <ul class="navbar-nav mr-auto main-items">
          <li class="nav-item active">
             <nuxt-link :to="localePath('index')" class="nav-link">
                  {{ $t('navbar.home') }}
              </nuxt-link>
          </li>
          <li class="nav-item">
            <a @click="toggleNavbar" class="nav-link" href="#me"> {{ $t('navbar.me') }}</a>
          </li>
          <li class="nav-item">
            <a @click="toggleNavbar" class="nav-link" href="#services"> {{ $t('navbar.services') }}</a>
          </li>
          <li class="nav-item">
            <a @click="toggleNavbar" class="nav-link" href="#portfolio"> {{ $t('navbar.portfolio') }}</a>
          </li>
          <li class="nav-item">
            <a @click="toggleNavbar" class="nav-link" href="#contact"> {{ $t('navbar.contact') }}</a>
          </li>
        </ul>

And for he contact buttons, after many years using a contact form (which as a user, is not my favorite way to contact) I decided to do with a simple email link this time, a regular mailto: link with a little trick to reduce the number of spam messages I get.

 <a class="btn button--default" href="mailto:hxexlxlo@cxoxdexwxithsxonxia.com" 
  onmouseover="this.href=this.href.replace(/x/g,'')">
    {{ $t("header.button") }}
  </a>

I have added some x to the email address to make it invalid and then, use an onmouseover event to remove the x and have my valid email address.

I'm pretty happy with my home page. Let's now move to the blog and add some final touches. But this will be in the next part of this article.

Or you can go directly to Part 5 if you want to know how to generate and deploy the static site.